IllogiGames:Sinkhole/cv dungeon 2

From Illogicopedia
Jump to navigation Jump to search

This is the python script, 'cv_dungeon_2', which is used to compile The Sinkhole


#!/usr/bin/python
#
# Convert the single file source using symbolic names to
# a single file using offsets
#
# Update log
#
# Aug  1 2014 S: Tuning, 'cause it's dog-slow.  One surprise:  Spent (*wasted*) a lot of time switching to using
#               compiled REs rather than inline REs.  Result was it ran slower.  Hmmph.
# Jul 27 2014 S: Converted to python yesterday.  Ran a script over it which probably did more harm than good
#               then hand edited it, line by line.  Whew.  Not sure it was worth it.  Still not working right.
# Jul 15 2014 S: Added attributes and item tags, jazzed up expression parsing to be slightly less broken+  Nested parens should work a bit
#		better, ' not =' might work now+
#

import sys
import re
import string
import time

debug = 0
optimize = 1
keep_comments = 0               # Set => well, you know +++ keep the comments+
dump_states = 0
generate_inventory = 1
verbose = 0
start_time = time.time()
do_digest_rooms = 1

class G:
    filename = None
    out_filename = None
    of = None
    segment_tags = ['', '-a', '-b', '-c', '-d', '-e', '-f', '-g', '-h', '-i', '-j',
                    '-k', '-l', '-m', '-n', '-o', '-p', '-q', '-r', '-s', '-t', '-u',
                    '-v', '-w', '-x', '-y', '-z',
                    '-aa', '-ab', '-ac', '-ad', '-ae', '-af', '-ag', '-ah', '-ai', '-aj',
                    '-ak', '-al', '-am', '-an', '-ao', '-ap', '-aq', '-ar', '-as', '-at', '-au',
                    '-av', '-aw', '-ax', '-ay', '-az']
    splitcount = 0
    index_only = 0
    sort_rooms = 0

    permutations = {}
    m = 0

# State related variables and stuff

    shared_ilist_for_state = {}	# Map of state number to inventory list number
    tag_to_inventory_item = {}			# Short tags for inventory items -- used in finding sets of states with given attributes
    tag_to_attribute = {}			# Index attribute tags
    header_count = 0                            # Value used to create unique (and useless) tag values for headers

    randtable_nv = 0

    last_state_expr = ""

# ****************************************************************
# Globals used during macro (and stuff) definition

    simple_macros = {}				# All macros are entered in the simple-macros table
    macro_args = {}				# A macro with arguments has an entry in the args table, as well

    pp_defining = 0
    pp_defining_state = 0
    pp_defining_attributes = 0
    pp_state_name = None
    pp_base_state_name = None
    pp_name = None
    pp_text = ""

    pp_new_inventory = [""];			# One blank line for the header text
    pp_new_attributes = []
    pp_new_inventory_flags = {}
    pp_new_attribute_flags = {}

    pp_nest_level = 0
    saved_pp_start_line = []
    saved_pp_condition_skipping = []
    saved_pp_inside_if = []
    saved_pp_skip_else = []
    saved_pp_skip_whole_thing = []

    pp_start_line = 0
    pp_condition_skipping = 0
    pp_inside_if = 0
    pp_skip_else = 0
    pp_skip_whole_thing = 0			# Set => we're skipping a nested if construct

class Mice(Exception):          # We throw mice if there's an error parsing a room
    pass

class Whoops:
    def __init__(self,str = None, throw=None):
        if str:
            print >>sys.stderr, "Whoops! " + str
        else:
            print >>sys.stderr, "Whoops!"
        if throw:
            raise throw
        if not str:
            self.val = throw
            raise self
        exit()
    pass

#
# The 'relation' class is a relation among attributes in a particular state.
# The relation must be satisfied for a combination to be legal.  We use
# this in deciding what new states to actually instantiate when we instantiate
# new states from new attribute lists.
#
class Relation:
    def __init__(self, rmask, rvalues):
        self.mask = rmask
        self.rvalues = rvalues
        pass
    #
    # Check a value to see if it conforms to this relation
    #
    def check_val (self, val):
        mv = val & self.mask
        for rv in self.rvalues:
            if mv == rv:
                return True
        return False

    def dump(self):
        print >>sys.stderr, "Relation: mask = %s. %s patterns:" % (bin(self.mask), len(self.rvalues))
        for r in self.rvalues:
            print >>sys.stderr, "   %s" % (bin(r))
    pass



class State:
    names = {}                  # Map to state numbers  Hashed by string, returns state object
    by_number = []              # Map from numbers to states
    def __init__(self, sname):
        try:
            os = State.names[sname]
            Whoops ("class State:  State %s, old number %s, being redefined as %s" % (sname, os.number, len(by_number)))
        except KeyError:
            pass
        self.name = sname
        self.ilist = []
        self.attributes = []
        self.number = len(State.by_number)
        State.names[sname] = self
        State.by_number.append (self)

        self.extra_ilist = []
        self.extra_attributes = []
        self.tag_to_number = {}   # Map from tags to numbers
        self.number_to_tag = []   # Array of tags
        self.relations = []
        self.equiv_inventory = {}
        self.equiv_attribute = {}
        pass
    #
    # Find a state which matches a list of tags
    #
    @staticmethod
    def tags_to_state (tags):
        tags = sorted(tags)     # Sort them so we have half a chance here
        for s in State.by_number:
            if sorted(s.ilist + s.attributes) == tags:
                return s
        return None
        pass

    #
    # Build a new state from the attributes we're given and the base state.
    # Called once for each automatically generated state when we instantiate a
    # state's extra inventory and attributes lists
    #
    def ia_build_state (self, lineno, bits):
        new_name = self.name + "_" + bits
        new_state = State(new_name)
        new_state.ilist = map (None, self.ilist)
        new_state.attributes = map (None, self.attributes)

        bits = list(reversed(bits)) # We need to index from the right.  Easiest is to just reverse it.

        for i in xrange(len(self.extra_ilist)):
            if bits[i] == '1':
                item = self.extra_ilist[i]
                if not item or re.match (r"\s$", item):
                    print >>sys.stderr, "  State %s extra inventory:" % self.name
                    for j in self.extra_ilist:
                        print >>sys.stderr, "     %s" % j
                    print >>sys.stderr, "  State %s extra attributes:" % self.name
                    for j in self.extra_attributes:
                        print >>sys.stderr, "     %s" % j
                    Whoops ("ia_build_state at line %s: Sticking a blank item on the inventory" % lineno)
                new_state.ilist.append (item)

        for i in xrange(len(self.extra_attributes)):
            if bits[i + len(self.extra_ilist)] == '1':
                new_state.attributes.append (self.extra_attributes[i])
        return new_state

    #
    # Instantiate the extra inventory and attributes lists for a state.
    # All relations must already have been defined.
    # This will create new states for all (allowed) combinations of the new attributes.
    #
    def instantiate_attributes(self, lineno):
        if debug:
            dprint ("instantiate_attributes (state=%s, line %s)" % (self.name, lineno))
        count = len(self.extra_ilist) + len(self.extra_attributes)
        if not count:
            Whoops ("instantiate_attributes(state=%s, at line %s):  Nothing to instantiate -- that doesn't seem right" % (self.name, lineno))
        new_states = []
        for i in xrange(1, 2**count):
            if debug:
                print >>sys.stderr, " .. Checking bitstring %s" % bin(i)
                print >>sys.stderr, "Relations: %s" % self.relations
            ok = True
            for r in self.relations: # Check each relation to see that it's satisified
                if not r.check_val (i):
                    ok = False
                    break
                pass
            if not ok:          # If it didn't pass the relation checks, don't instantiate it
                if debug:
                    print >>sys.stderr, ("Failed the check.")
                continue
            # Need to convert to binary.  Make a bit string, strip the leading '0b' or other garbage we're given.
            bs = re.sub (r".*?([0-1]+)$", r"\1", bin(i))
            bs = '0' * (count - len(bs)) + bs # Pad on the left with extra zeros.
            new_states.append (self.ia_build_state (lineno, bs))

        for s in new_states:     # And now, let's process the equiv relations by beefing up the ilists and attribute lists with the dupes
            for inv in s.ilist:
                try:
                    eqv = self.equiv_inventory[inv]
                    for ni in eqv:
                        s.ilist.append (ni)
                except:
                    pass
            for att in s.attributes:
                try:
                    eqv = self.equiv_attribute[att]
                    for na in eqv:
                        s.attributes.append (na)
                except:
                    pass
        pass

    #
    # Set up the attribute numbers for the new attributes from which we're going
    # to be creating new states.  Note that this is a purely local affair -- these
    # are *just* the numbers for the extra attributes for this state, and they're
    # *only* going to be used for building (and naming) new states right now, and then they'll
    # be discarded.
    #
    def establish_attribute_numbers(self):
        self.tag_to_number = {}
        self.number_to_tag = []
        for at in self.extra_ilist:
            self.tag_to_number[at] = len(self.number_to_tag)
            self.number_to_tag.append (at)
        for at in self.extra_attributes:
            self.tag_to_number[at] = len(self.number_to_tag)
            self.number_to_tag.append (at)
        pass

    # Relations can be:
    #
    # t1 + t2 + ...             # At least one must be present (Requiring all to be present wouldn't be useful!)
    # t1 ! t2 ! ...             # No more than one must be present
    # t1 -> t2 & t3 & ...     # If t1 is present, all of t2, ... must be present, too
    # t1 =  t2 & t3 & ...      # Same as previous, but if all of t2, t3,... are present, t1 must be, too.
    # t1 -> t2 + t3 + ...     # If t1 is present, at least one of of t2, ... must be present, too
    # t1 =  t2 + t3 + ...      # Same as previous, but if any of t2, t3, ... are present, t1 must be, too
    #
    # Mix and match combinations are not defined.
    #
    def define_one_relation (self, lineno, str):
        #
        # All zeroes are disallowed.  All other combinations are OK.
        #
        if re.match (r"\s* \w* \s* \+", str, re.X): # Does it start with 't1 + ...' ?
            tl = re.split (r"\s*\+\s*", str, re.X)
            try:
                tnums = [self.tag_to_number[x] for x in tl]
            except KeyError:
                print >>sys.stderr, "   Split list: %s" % tl
                print >>sys.stderr, "   Tag to number: '%s'" % self.tag_to_number
                print >>sys.stderr, "   Extra ilist: '%s'" % self.extra_ilist
                Whoops ("define_one_relation, at line %s: String '%s' contains tags we don't recognize" % (lineno, str), "Roaches")
            tbits = [2**x for x in tnums] # Convert the tag numbers to tag masks
            mask = reduce (lambda x,y: x+y, tbits)
            if debug:
                dprint (" .. Handing %s to allmasks" % tbits)
            values = (self.allmasks (tbits))[1:] # Drop the first element, which is always zero.
            r = Relation (mask, values)
            if debug:
                print >>sys.stderr, "Relation at line %s, string %s:" % (lineno, str)
                r.dump()
            return r

        #
        # All zeros is OK, or any single bit can be set.
        #
        elif re.match (r"\s* \w* \s* \!", str, re.X): # Does it start with 't1 ! ...' ?
            tl = re.split (r"\s*\!\s*", str, re.X)
            try:
                tnums = [self.tag_to_number[x] for x in tl]
            except KeyError:
                Whoops ("define_one_relation, at line %s: String '%s' contains tags we don't recognize" % (lineno, str))
            tbits = [2**x for x in tnums] # Convert the tag numbers to tag masks
            mask = reduce (lambda x,y: x+y, tbits)
            values = [0] + tbits # Allowable values are none at all, and any single entry from the list
            r = Relation (mask, values)
            if debug:
                print >>sys.stderr, "Relation at line %s, string %s:" % (lineno, str)
                r.dump()
            return r

        #
        # If the lhs is present, all bits must be set.  We may be missing all "&" terms on the right, please note.
        #
        elif save_match (r"\s* (\w*?) \s*\-\>\s* (\w* (?:\s* \& .*?)? ) \s*", str):
            lhs = G.m.group(1)
            tl = re.split (r"\s*\&\s*", G.m.group(2), re.X)
            print >>sys.stderr, "Attr relation with -> & : lhs = '%s', rhs = '%s'" % (lhs,tl)
            try:
                tnums = [self.tag_to_number[x] for x in tl]
                lhs_tnum = self.tag_to_number[lhs]
            except KeyError:
                Whoops ("define_one_relation, at line %s: String '%s' contains tags we don't recognize" % (lineno, str))
            tbits = [2**x for x in tnums] # Convert the tag numbers to tag masks
            lhs_bit = 2**lhs_tnum
            mask = lhs_bit + reduce (lambda x,y: x+y, tbits)
            if debug:
                dprint (" .. Handing %s to allmasks" % tbits)
            values = [mask] + self.allmasks (tbits) # Allow all combinations without the LHS (including 0), and allow all bits set.
            r = Relation (mask, values)
            if debug:
                print >>sys.stderr, "Relation at line %s, string %s:" % (lineno, str)
                r.dump()
            return r

        #
        # If the lhs is present, all bits must be set.
        # If all bits are set on the rhs, the lhs must be present.
        #
        elif save_match (r"\s* (\w*?) \s*\=\s* (\w* \s* \& .*? ) \s*", str):
            lhs = G.m.group(1)
            tl = re.split (r"\s*\&\s*", G.m.group(2), re.X)
            try:
                tnums = [self.tag_to_number[x] for x in tl]
                lhs_tnum = self.tag_to_number[lhs]
            except KeyError:
                Whoops ("define_one_relation, at line %s: String '%s' contains tags we don't recognize" % (lineno, str))
            tbits = [2**x for x in tnums] # Convert the tag numbers to tag masks
            lhs_bit = 2**lhs_tnum
            mask = lhs_bit + reduce (lambda x,y: x+y, tbits)
            if debug:
                dprint (" .. Handing %s to allmasks" % tbits)
            values = [mask] + self.allmasks (tbits) # Allow all combinations without the LHS (including 0), and allow all bits set.
            values = values[:-1]                     # Drop the last one, which is all bits set EXCEPT for lhs, which is what we disallow
            r = Relation (mask, values)
            if debug:
                print >>sys.stderr, "Relation at line %s, string %s:" % (lineno, str)
                r.dump()
            return r
        #
        # If the lhs is present, SOME bits from the rhs must be set.
        #
        elif save_match (r"\s* (\w*?) \s*\-\>\s* (\w* \s* \+ .*?) \s*", str):
            lhs = G.m.group(1)
            tl = re.split (r"\s*\+\s*", G.m.group(2), re.X)
            try:
                tnums = [self.tag_to_number[x] for x in tl]
                lhs_tnum = self.tag_to_number[lhs]
            except KeyError:
                Whoops ("define_one_relation, at line %s: String '%s' contains tags we don't recognize" % (lineno, str))
            tbits = [2**x for x in tnums] # Convert the tag numbers to tag masks
            lhs_bit = 2**lhs_tnum
            mask = reduce (lambda x,y: x+y, tbits)
            if debug:
                dprint (" .. Handing %s to allmasks" % tbits)
            values = self.allmasks (tbits)                # All combinations of the terms on the RHS (including 0)
            nz_values = values[1:]                   # All combinations of the RHS *except* 0 (which was the first element)
            ok_values = [lhs_bit + x for x in nz_values] # All combinations of nonzero RHS with the LHS
            values = values + ok_values                  # Combine these with all cominations which didn't include the LHS
            r = Relation (lhs_bit + mask, values)
            if debug:
                print >>sys.stderr, "Relation at line %s, string %s:" % (lineno, str)
                r.dump()
            return r
        #
        # If the lhs is present, SOME bits from the rhs must be set.
        # If ANY bits are set on the RHS the LHS must be present, too
        #
        elif save_match (r"\s* (\w*?) \s*\=\s* (\w* \s* \+ .*?) \s*", str):
            lhs = G.m.group(1)
            tl = re.split (r"\s*\+\s*", G.m.group(2), re.X)
            try:
                tnums = [self.tag_to_number[x] for x in tl]
                lhs_tnum = self.tag_to_number[lhs]
            except KeyError:
                Whoops ("define_one_relation, at line %s: String '%s' contains tags we don't recognize" % (lineno, str))
            tbits = [2**x for x in tnums] # Convert the tag numbers to tag masks
            lhs_bit = 2**lhs_tnum
            mask = reduce (lambda x,y: x+y, tbits)
            if debug:
                dprint (" .. Handing %s to allmasks" % tbits)
            values = self.allmasks (tbits)                # All combinations of the terms on the RHS (including 0)
            nz_values = values[1:]                   # All combinations of the RHS *except* 0 (which was the first element)
            ok_values = [lhs_bit + x for x in nz_values] # All combinations of nonzero RHS with the LHS
            values = [0] + ok_values                  # Nothing at all, or some combo of the LHS and some RHS bits
            r = Relation (lhs_bit + mask, values)
            if debug:
                print >>sys.stderr, "Relation at line %s, string %s:" % (lineno, str)
                r.dump()
            return r
        else:
            Whoops ("Defining a relation at line %s:  Don't understand string %s" % (lineno, str))
        pass

    # 
    # Form all possible masks from a collection of input bits
    # Implements the "t1 + t2 + ..." function.
    # Note that the first element on the returned list will be zero, and the last will include all bits.
    #
    @staticmethod
    def allmasks (vals):
        if len(vals) <= 1:
            return [0, vals[0]]

        first = vals[0]
        submasks = State.allmasks (vals[1:]) # Recurse, finding all masks of cdr(passed-list)
        masks = map (None, submasks)   # Copy the submasks list
        for m in submasks:
            masks.append (m + first) # Append everything with the extra bit set.  All bits set brings up the rear.
        return masks

    @staticmethod
    def dump_states():
        print >>sys.stderr, "Dump of %s states:" % len(State.by_number)
        for s in State.by_number:
            print >>sys.stderr, "State %s (number %s):" % (s.name, s.number)
            print >>sys.stderr, "  Ilist:       '%s'" % sorted(s.ilist)
            print >>sys.stderr, "  Attributes:  '%s'" % sorted(s.attributes)
            print >>sys.stderr, "  Extra ilist: '%s'" % sorted(s.extra_ilist)
            print >>sys.stderr, "  Extra attrs: '%s'" % sorted(s.extra_attributes)
            print >>sys.stderr, "  Tags->num:   '%s'" % s.tag_to_number 
            print >>sys.stderr, "  Num->tag:    '%s'" % s.number_to_tag
            print >>sys.stderr, "  Relations:"
            for r in s.relations:
                print >>sys.stderr, "    Mask: '%s'" % r.mask
                for v in r.rvalues:
                    print >>sys.stderr, "        v: '%s'" % v
            pass
    pass

class Room:
    next_roomno = 0
    max_room_no = -1

    def __init__(self, name, lineno, number):
        self.name = name
        self.lineno = lineno
        self.number = number

    @staticmethod
    def alloc_roomno():
        nr = Room.next_roomno
        Room.next_roomno += 1
        if (Room.next_roomno > Room.max_room_no):
            Room.max_room_no = Room.next_roomno
        return nr

    @staticmethod
    def reset_roomno():
        Room.next_roomno = 0
    pass
#
# We build the input rooms on a pass over the file, and then iterate expanding them.
#
class Input_Room(Room):
    irooms = []                 # In creation order
    irooms_by_name = {}         # Hashed by name
    ##built_room_lists = {}       # Hashed by truncated name
    ##required_rooms = []
    ##work_queue = []             # Truncated names of rooms to be worked on
    
    def __init__(self, name, lineno):
        Room.__init__ (self, name, lineno, None)
        ##self.digested = -1
        try:
            Input_Room.irooms_by_name[name]
            Whoops ("Input_Room:  Room %s at line %s was already defined" % (name, lineno))
        except KeyError:
            Input_Room.irooms.append (self)
            Input_Room.irooms_by_name[name] = self
        self.itext = []
        pass

        ##short_name = re.sub (r"(.*?)[A-Z0-9]*-[0-9]*$", r"\1", name) # Trim trailing uppercaseletters and numbers
        ##dprint ("Input room %s:  Putting short name %s on the built room list" % (name, short_name))
        ##try:                    # Put it on the built room list for this name
        ##    rlist = Input_Room.built_room_lists[short_name]
        ##    rlist.append (self)
        ##except KeyError:
        ##    Input_Room.built_room_lists[short_name] = [self] # Start the list for this name

    ##@staticmethod
    ##def put_on_work_queue (name):
    ##    name = re.sub (r"(.*?)[A-Z0-9]*$", r"\1", name) # Trim trailing uppercase letters and numbers
    ##    Input_Room.work_queue.append (name)
    ##    pass

# We build an array of output rooms, sort an index to it, and then write it out.
# We plant to garbage collect it before we write it out, which makes us far less
# subject to room inflation as a result of bad state generation algorithms.
#
class Output_Room(Room):
    orooms = {}                 # By name
    undefs = {}                 # By name.  Value is meaningless nonzero flag.
    referenced_undefs = {}      # By name.
    create_order = []           # All output rooms, in creation order

    def __init__(self, name, lineno, number):
        Room.__init__ (self, name, lineno, number)
        try:
            Output_Room.orooms[name]
            Whoops ("Output_Room:  Room %s at line %s was already defined" % (name, lineno))
        except KeyError:
            Output_Room.orooms[name] = self
            Output_Room.create_order.append (self) # And put it on the unordered list, too.
        self.otext = ""
        self.ordinal = None   # This is the value we'll actually index the rooms by when we emit them
        self.used = None      # Nobody uses this room (yet)
        self.links = {}       # Links to other rooms
        self.bad_links = {}   # Links to nonexistent rooms
        self.gc = None        # GC bit for walking the tree
        self.segment = 0
    #
    # Find all rooms that are pre-marked 'used' and put them on the oblist,
    # then call gc_walk on each room on the oblist
    #
    @staticmethod
    def gc ():
        oblist = []
        used_count = 0
        for r in Output_Room.orooms.values():
            r.gc = None
            if r.used:
                oblist.append(r)
        for r in oblist:
            used_count += r.gc_walk()
        return used_count

    #
    # Recurse down the tree from this room, marking everything we find as 'used'
    # Mark each room as 'gc' when we get there so we know where to stop
    #
    def gc_walk (self):
        if self.gc:
            return 0
        used_count = 1
        self.used = 1
        self.gc = 1
        for rn in self.links.keys():
            used_count += Output_Room.orooms[rn].gc_walk()
        for rn in self.bad_links.keys():
            try:
                used_count += Output_Room.orooms[rn].gc_walk() # Check the link -- see if it's still bad (might have been fixed later in the phase)
                continue                        # Nothing to do for it if it's not bad.
            except KeyError:
                pass
            try:
                refs = Output_Room.referenced_undefs[rn] # Has it been reported already?
                rcount = refs[2] + 1
                refs = refs[:-1] + [rcount]
            except:                               # If not, record the place it's referenced
                Output_Room.referenced_undefs[rn] = (self.lineno, self.name, 1)
            pass
        return used_count
        pass
                
#
# Some random numbers -- generated by C's 'random' function, which is supposed to be pretty good+
#
#
# NB -- this was supposed to help with the 'scramble' function, which is used to build the (static) links in the
# desert maze+  It didn't+  The result still stinks+  Either the concept is NG or a better way of generating random
# links is needed+
#
randoms = [1804289383, 846930886, 1681692777, 1714636915, 1957747793, 424238335, 719885386, 1649760492, 596516649, 1189641421, 1025202362,
	       1350490027, 783368690, 1102520059, 2044897763, 1967513926, 1365180540, 1540383426, 304089172, 1303455736, 35005211, 521595368,
	       294702567, 1726956429, 336465782, 861021530, 278722862, 233665123, 2145174067, 468703135, 1101513929, 1801979802, 1315634022,
	       635723058, 1369133069, 1125898167, 1059961393, 2089018456, 628175011, 1656478042, 1131176229, 1653377373, 859484421, 1914544919,
	       608413784, 756898537, 1734575198, 1973594324, 149798315, 2038664370, 1129566413, 184803526, 412776091, 1424268980, 1911759956,
	       749241873, 137806862, 42999170, 982906996, 135497281, 511702305, 2084420925, 1937477084, 1827336327, 572660336, 1159126505,
	       805750846, 1632621729, 1100661313, 1433925857, 1141616124, 84353895, 939819582, 2001100545, 1998898814, 1548233367, 610515434,
	       1585990364, 1374344043, 760313750, 1477171087, 356426808, 945117276, 1889947178, 1780695788, 709393584, 491705403, 1918502651,
	       752392754, 1474612399, 2053999932, 1264095060, 1411549676, 1843993368, 943947739, 1984210012, 855636226, 1749698586, 1469348094,
	       1956297539, 1036140795, 463480570, 2040651434, 1975960378, 317097467, 1892066601, 1376710097, 927612902, 1330573317, 603570492,
	       1687926652, 660260756, 959997301, 485560280, 402724286, 593209441, 1194953865, 894429689, 364228444, 1947346619, 221558440,
	       270744729, 1063958031, 1633108117, 2114738097, 2007905771, 1469834481, 822890675, 1610120709, 791698927, 631704567, 498777856,
	       1255179497, 524872353, 327254586, 1572276965, 269455306, 1703964683, 352406219, 1600028624, 160051528, 2040332871, 112805732,
	       1120048829, 378409503, 515530019, 1713258270, 1573363368, 1409959708, 2077486715, 1373226340, 1631518149, 200747796, 289700723,
	       1117142618, 168002245, 150122846, 439493451, 990892921, 1760243555, 1231192379, 1622597488, 111537764, 338888228, 2147469841,
	       438792350, 1911165193, 269441500, 2142757034, 116087764, 1869470124, 155324914, 8936987, 1982275856, 1275373743, 387346491,
	       350322227, 841148365, 1960709859, 1760281936, 771151432, 1186452551, 1244316437, 971899228, 1476153275, 213975407, 1139901474,
	       1626276121, 653468858, 2130794395, 1239036029, 1884661237, 1605908235, 1350573793, 76065818, 1605894428, 1789366143, 1987231011,
	       1875335928, 1784639529, 2103318776, 1597322404, 1939964443, 2112255763, 1432114613, 1067854538, 352118606, 1782436840, 1909002904,
	       165344818, 1395235128, 532670688, 1351797369, 492067917, 1504569917, 680466996, 706043324, 496987743, 159259470, 1359512183,
	       480298490, 1398295499, 1096689772, 2086206725, 601385644, 1172755590, 1544617505, 243268139, 1012502954, 1272469786, 2027907669,
	       968338082, 722308542, 1820388464, 933110197, 6939507, 740759355, 1285228804, 1789376348, 502278611, 1450573622, 1037127828,
	       1034949299, 654887343, 1529195746, 392035568, 1335354340, 87755422, 889023311, 1494613810, 1447267605, 1369321801, 745425661,
	       396473730, 1308044878, 1346811305, 1569229320, 705178736, 1590079444, 434248626, 1977648522, 1470503465, 1402586708, 552473416,
	       1143408282, 188213258, 559412924, 1884167637, 1473442062, 201305624, 238962600, 776532036, 1238433452, 1273911899, 1431419379,
	       620145550, 1665947468, 619290071, 707900973, 407487131, 2113903881, 7684930, 1776808933, 711845894, 404158660, 937370163,
	       2058657199, 1973387981, 1642548899, 1501252996, 260152959, 1472713773, 824272813, 1662739668, 2025187190, 1967681095, 1850952926,
	       437116466, 1704365084, 1176911340, 638422090, 1943327684, 1953443376, 1876855542, 1069755936, 1237379107, 349517445, 588219756,
	       1856669179, 1057418418, 995706887, 1823089412, 1065103348, 625032172, 387451659, 1469262009, 1562402336, 298625210, 1295166342,
	       1057467587, 1799878206, 1555319301, 382697713, 476667372, 1070575321, 260401255, 296864819, 774044599, 697517721, 2001229904,
	       1950955939, 1335939811, 1797073940, 1756915667, 1065311705, 719346228, 846811127, 1414829150, 1307565984, 555996658, 324763920,
	       155789224, 231602422, 1389867269, 780821396, 619054081, 711645630, 195740084, 917679292, 2006811972, 1253207672, 570073850,
	       1414647625, 1635905385, 1046741222, 337739299, 1896306640, 1343606042, 1111783898, 446340713, 1197352298, 915256190, 1782280524,
	       846942590, 524688209, 700108581, 1566288819, 1371499336, 2114937732, 726371155, 1927495994, 292218004, 882160379, 11614769,
	       1682085273, 1662981776, 630668850, 246247255, 1858721860, 1548348142, 105575579, 964445884, 2118421993, 1520223205, 452867621,
	       1017679567, 1857962504, 201690613, 213801961, 822262754, 648031326, 1411154259, 1737518944, 282828202, 110613202, 114723506,
	       982936784, 1676902021, 1486222842, 950390868, 255789528, 1266235189, 1242608872, 1137949908, 1277849958, 777210498, 653448036,
	       1908518808, 1023457753, 364686248, 1309383303, 1129033333, 1329132133, 1280321648, 501772890, 1781999754, 150517567, 212251746,
	       1983690368, 364319529, 1034514500, 484238046, 1775473788, 624549797, 767066249, 1886086990, 739273303, 1750003033, 1415505363,
	       78012497, 552910253, 1671294892, 1344247686, 1795519125, 661761152, 474613996, 425245975, 1315209188, 235649157, 1448703729,
	       1679895436, 1545032460, 430253414, 861543921, 677870460, 932026304, 496060028, 828388027, 1144278050, 332266748, 1192707556,
	       31308902, 816504794, 820697697, 655858699, 1583571043, 559301039, 1395132002, 1186090428, 1974806403, 1473144500, 1739000681,
	       1498617647, 669908538, 1387036159, 12895151, 1144522535, 1812282134, 1328104339, 1380171692, 1113502215, 860516127, 777720504,
	       1543755629, 1722060049, 1455590964, 328298285, 70636429, 136495343, 1472576335, 402903177, 1329202900, 1503885238, 1219407971,
	       2416949, 12260289, 655495367, 561717988, 1407392292, 1841585795, 389040743, 733053144, 1433102829, 1887658390, 1402961682,
	       672655340, 1900553541, 400000569, 337453826, 1081174232, 1780172261, 1450956042, 1941690360, 410409117, 847228023, 1516266761,
	       1866000081, 1175526309, 1586903190, 2002495425, 500618996, 1989806367, 1184214677, 2004504234, 1061730690, 1186631626, 2016764524,
	       1717226057, 1748349614, 1276673168, 1411328205, 2137390358, 2009726312, 696947386, 1877565100, 1265204346, 1369602726, 1630634994,
	       1665204916, 1707056552, 564325578, 1297893529, 1010528946, 358532290, 1708302647, 1857756970, 1874799051, 1426819080, 885799631,
	       1314218593, 1281830857, 1386418627, 1156541312, 318561886, 1243439214, 70788355, 1505193512, 1112720090, 1788014412, 1106059479,
	       241909610, 1051858969, 1095966189, 104152274, 1748806355, 826047641, 1369356620, 970925433, 309198987, 887077888, 530498338,
	       873524566, 37487770, 1541027284, 1232056856, 1745790417, 1251300606, 959372260, 1025125849]

# ****************************************************************
# Tell how it works

def usage() :
    print >>sys.stderr, "Usage: cv_dungeon_2 [args]"
    print >>sys.stderr, "    -index   -- Dump the room index, and stop"
    print >>sys.stderr, "    -sort    -- Sort the rooms before indexing them (default)"
    print >>sys.stderr, "    -debug   -- Turn on debug output"
    print >>sys.stderr, "    -verbose -- Show all unused or undefined rooms (not just 'interesting' ones)"
    print >>sys.stderr, "    -f  <filename>  -- Use <filename> for the input file"
    print >>sys.stderr, "    -o  <filename>  -- Write output to <filename>"
    print >>sys.stderr, "    -split <count>  -- Split the output into <count> files"
    print >>sys.stderr, "    -opt, -no_opt   -- Optimize (or don't) the output.  Without optimization, the output is useless but more readable."
    print >>sys.stderr, "    -g              -- same as '-no_opt'"
    print >>sys.stderr, "    -keep_comments  -- don't strip comments (result is probably useless but it can help with debugging)"
    print >>sys.stderr, "    -dump_states    -- dump the state table"
    print >>sys.stderr, "    -no_inventory   -- Skip dumping the inventory rooms"
    print >>sys.stderr, "    -no_digest      -- Skip the digestion step"
    pass

# ****************************************************************
# Process arguments

an = 1
while an < len(sys.argv):
    a1 = sys.argv[an]
    an += 1
    if a1 == "-index" :
	G.index_only = 1
    elif a1 == "-sort" :
	G.sort_rooms = 1
    elif a1 == "-debug" :
	debug = 1
    elif a1 == "-verbose" :
	verbose = 1
    elif a1 == "-f" :
	G.filename = sys.argv[an]
        an += 1
    elif a1 == "-o":             # Output filename
        G.out_filename = sys.argv[an]
        an += 1
    elif a1 == "-split":        # Split count
        G.splitcount = int(sys.argv[an])
        an += 1
    elif a1 == "-opt":
	optimize = 1
    elif a1 == "-no_opt" or a1 == "-g":
	optimize = 0
    elif a1 == "-keep_comments":
	keep_comments = 1
    elif a1 == "-dump_states":
        dump_states = 1
    elif a1 == "-no_inventory":
        generate_inventory = 0
    elif a1 == "-no_digest":
        do_digest_rooms = 0
    elif not G.filename:
        G.filename = a1
    else :
        usage()
	Whoops ("Argument %s not understood\n" % (a1))

if not G.filename:
    usage()
    Whoops ("No filename was provided")
   
# ****************************************************************
# Strip whitespace -- this is a surprisingly big headache

def wstrip(str) :
    ##m = re.match (r"\s* (.*?) \s*$", str, re.X) # Strip white space off the ends of the pair list -- it messes up the number parsing
    ##if m:
    ##    str = m.group(1)
    return string.strip(str)    # Yo ho.  Shooda nown it was a BIF.
    
# ****************************************************************
# Strip parens.  If there are nested sets of useless parens this strips all of them.

def pstrip(str, ptype) :
    if ptype == "()":
        expr = r"\s*\(\s* ([^\(\)]*?) \s*\)\s*$"
    elif ptype == "[]":
        expr = r"\s*\[\s* ([^\[\]]*?) \s*\]\s*$"
    else:
        Whoops ("pstrip: Stripping '%(str)s': Don't understand what you want me to strip: ptype = '%(ptype)s'" % vars())

    while 1:
        m = re.match (expr, str, re.X) # Strip white space off the ends of the pair list -- it messes up the number parsing
        if m:
            str = m.group(1)
        else:
            return wstrip(str)
    
# ****************************************************************
# Print a string if we're in debug mode

def dprint(str) :
    if (debug) :
	print >>sys.stderr, str
        sys.stderr.flush()

# ****************************************************************
# Return the next random number in the random table

def rand_next() :
    rv = randoms[G.randtable_nv]
    G.randtable_nv += 1
    if G.randtable_nv >= len(randoms):
        G.randtable_nv = 0        # Wrap if we fall off the end
    return rv

# ****************************************************************
# Find the member of a permutation+  This is pretty bizarre, but I hope the purpose
# will make sense+
#
# It takes three numbers for arguments: Permutation, length, and member:
#
# permutation_next (perm#, length, member):
#
#   perm# -- the permutation+  Each time it's called with the same value of perm# it uses the
#				same permutation+
#   length -- Length of the permutation
#   member -- The member of the permutation+ (The member *value* _not_ the index+)
#
# It searches the permutation for 'member', and returns the *next* value in the permutatoin+
#
# The purpose is generating a random shuffle of rooms+  You decide on a permutation number for
# the shuffle, and decide how many rooms are in the shuffle, and then for each room, you
# call it with the permutation number, the length, and the room number+  And you link to
# the room number it returns, which is the next room in the shuffle+
#
# The first time it's called for a permutation number it computes the permutation, using
# our random number table (or something else which always returns the same sequence of
# random numbers), and stores it in a hash for later re-use+

def permutation_next (pindex, ln, member) :
    member = int(member)
    if debug:
        print >>sys.stderr, "permutation_next (%s, %s, %s)" % (pindex, ln, member)
    try:
        p = G.permutations[pindex]
    except KeyError:
	p = range (ln);			# Build a sequence
        for i in xrange(1,ln):		# Shuffle it
	    j = rand_next() % (i+1)
            swap = p[i]                 # Swap 'em
            p[i] = p[j]
            p[j] = swap
	
	if (debug) :
	    print >>sys.stderr, " .. Perm %(pindex)s not defined, so we built it" % vars()
	    sep = "  "
	    for pv in p:
		print >>sys.stderr, "%(sep)s%(pv)s" % vars(),
		sep = ", "
	    print >>sys.stderr, ""
	
	G.permutations[pindex] = p		# Retain a pointer to it
    
    if len(p) != ln:
        Whoops ("Permutation pindex has length %s, passed length is %s; member is %s" % (len(p), ln, member))

    for m in xrange(len(p)) :
	if (p[m] == member) :
	    nindex = (m + 1) % ln
	    if debug:
                print >>sys.stderr, " .. Next index %s; returning %s" % (nindex, p[nindex])
	    return "%s" % p[nindex]                     # Return a string

    Whoops ("permutation_next(%s, %s, %s) fell off the end of the permutation" % (pindex, ln, member))

# ****************************************************************
# Find all states with a particular set of tags (items or attributes)
# Returns the list of numbers for the states it found

sef_fs_cache = {}

def sef_find_states_with_tags(lineno, args) :
    global sef_fs_cache
    try:
        res = sef_fs_cache[args]
        return res
    except KeyError:
        pass

    scount = len(State.by_number)
    if debug:
        dprint ("sef_find_states_with_tags at line %(lineno)s with args '%(args)s'" % vars())
        dprint (" .. sef_find_states_with_tags: We will search %(scount)s states" % vars())
    states = State.by_number    # We start with the list of all states, and we'll winnow it down

    tags = re.split(r"\s*,\s*",args)		# And all the tags we were passed
    for t in tags :
        if debug:
            dprint (" .. sf_find_states_with_tags: Processing tag %s" % (t))
        old_states = states     # Save off last time's state list
	states = [];                           # Clear the found states list for each new tag
	is_inventory_item = 1;                  # Assume it's from the inventory
        try:
            G.tag_to_inventory_item[t]
        except KeyError:
	    is_inventory_item = 0;              # Wasn't in the inventory -- must be an attribute
            try:
                G.tag_to_attribute[t]
            except:
                Whoops ("sef_find_states_with_tags: line %(lineno)s: Can't find tag %(t)s in inventory or value list" % vars())
            
	for s in old_states :                # For each state...
	    foundit = 0
	    
	    if is_inventory_item :
		l = s.ilist              # Get the inventory list for the state
            else :
		l = s.attributes  # It's an attribute, so get the attribute list
	    
	    for f in l:                 # Iterate over the inventory or attribute list and see if the specified item is on it
		if f == t :
		    foundit = 1
		    break

	    if foundit:                 # Did we find the attribute/inventory item in the state's lists?
                if debug:
                    dprint (" ++ sef_find_states_with_tags: State %(s)s looks good" % vars())
		states.append(s)

    scount = len(states)
    if debug:
        dprint (" ++ sef_find_states_with_tags: Found %(scount)s states which matched" % vars())

    res = [x.number for x in states] # Return a list of state numbers
    sef_fs_cache[args] = res         # Cache it to save time later
    return res

# ****************************************************************
# For each state in the given list, add a collection of attributes and
# look for a state which matches that.
# The new list will be in the same order as the old list, with each state on the
# new list being built from the corresponding state on the old list
#
# do_add = 0 => remove attributes
# do_add = 1 => add them
# do_add = 2 => remove some and add some, two lists are separated by a semicolon
#

sef_asa_cache = {}

def sef_add_sub_attribute (lineno, args, do_what) :
    global sef_asa_cache
    if debug:
        dprint ("sef_add_sub_attribute at line %s with args '%s', do_what=%s" % (lineno, args, do_what))

    m = re.match (r"(.*?) , ([^\(\)\[\]]*)$", args, re.X)   # We expect an expression, a comma, and a comma separated simple list of state names
    if not m :  
        Whoops ("state_expr_function_parser: line %(lineno)s: can't parse 'add_attribute' function arguments '%(args)s'\n" % vars())

    sexpr = wstrip (m.group(1))
    attr_str = wstrip (m.group(2))                  # Strip blanks
    state_str = pstrip (state_expression_parser (lineno, sexpr), "[]") # Strip brackets (which should be there)

    # Note that we need to parse the LHS before hashing it, because it's PROBABLY "*" which is context dependent.
    ckey = "%s<>%s|%s" % (state_str,args,do_what)      # Build a hash key from the parsed LHS expression, the rest of the argument string, and 'do_what'
    try:
        res = sef_asa_cache[ckey]
        return res
    except KeyError:
        pass

    old_states = re.split (r"\s*,\s*", state_str);                   # The state list we'll process

    if do_what != 2:
        new_attrs = re.split (r"\s*,\s*", attr_str);                           # And split the pairs up
    else:
        m = re.match (r"([^\:]*?)\s*\:\s*([^\:]*)", attr_str)
        if not m:
            Whoops ("Replacing attributes at line %s: Argument string %s is invalid" % (lineno, args))
        new_attrs = re.split (r"\s*,\s*", m.group(1)); # First group of attributes
        new_attrs_2 = re.split (r"\s*,\s*", m.group(2)); # Second group of attributes

    for na in new_attrs:        # A little argument checking!
        try:
            G.tag_to_inventory_item[na]
        except KeyError:
            try:
                G.tag_to_attribute[na]
            except KeyError:
                Whoops ("Adding/removing attributes at line %s, with args '%s': Attribute '%s' is unknown" % (lineno, args, na))
        pass
    if do_what == 2:
        for na in new_attrs_2:        # A little *more* argument checking!
            try:
                G.tag_to_inventory_item[na]
            except KeyError:
                try:
                    G.tag_to_attribute[na]
                except KeyError:
                    Whoops ("Adding/removing attributes at line %s, with args '%s': Attribute '%s' is unknown" % (lineno, args, na))
            pass

    new_states = []
    for sn in old_states:
        if not re.match (r"\d*$", sn):
            Whoops ("Adding attributes at line %s:  Expected a state number, found '%s'" % (lineno, sn))
        s = State.by_number[int(sn)]
        if do_what == 1:         # Adding one or more attributes
            raw = s.ilist + s.attributes + new_attrs
            raw.sort()
            fingerprint = [raw[0]]
            for i in xrange (1,len(raw)):      # Filter out dupes
                if raw[i] != raw[i-1]:
                    fingerprint.append (raw[i])
        elif do_what == 0 or do_what == 2:                 # In this case we're removing one or more attributes from each state
            fingerprint = []
            ##print >>sys.stderr, "sub_attributes: checking sub list '%s'" % new_attrs
            for attr in sorted (s.ilist + s.attributes): # Start with all the inventory items and attributes
                fingerprint.append (attr)                # Stick it on, hope it stays
                for rem in new_attrs:                    # But only keep them if we do NOT find them in new_attrs
                    if attr == rem:
                        ##print >>sys.stderr, "Removing attr %s from list '%s'" % (rem, fingerprint)
                        fingerprint.pop()                # Found on the list to be removed => remove it again
                        break
            pass

        if do_what == 2:
            raw = fingerprint + new_attrs_2 # We'll build a new list, starting with fingerprint
            raw.sort()
            fingerprint = [raw[0]]
            for i in xrange (1,len(raw)):      # Filter out dupes
                if raw[i] != raw[i-1]:
                    fingerprint.append (raw[i])
            pass
            
        new_state = State.tags_to_state (fingerprint)
        if not new_state:
            Whoops ("Adding or removing attributes at line %s: argument attributes are %s: Can't find a state matching '%s'" %
                    (lineno, new_attrs, fingerprint), Mice())
        new_states.append (new_state.number)
        pass

    sef_asa_cache[ckey] = new_states
    return new_states

# ****************************************************************
# Parse the state list operators
# Somebody has already expanded the lists all the way to state numbers (or so we hope)
# If this is called with a compound expression for left or right it will mess up.
#

def state_expr_operator_parser(lineno, lhs, op, rhs) :
    if debug:
        dprint ("state_expr_operator_parser: '%(lhs)s', '%(op)s', '%(rhs)s'" % vars())
    m = re.match (r"\s*\[\s*(.*)\s*\]\s*", lhs) # re.match => match from the string start.  We're looking for [...] expressions
    if m:
        lhs = m.group(1)
    m = re.match (r"\s*\[\s*(.*)\s*\]\s*", rhs)
    if m:
        rhs = m.group(1)
 
    left_list = re.split (r"\s*,\s*",lhs)
    right_list = re.split (r"\s*,\s*",rhs)

    if op == "+" or op == "|" :                 # Everything on either list
	for right_val in right_list :
	    foundit = 0
	    for j in left_list :
		if j == right_val :
		    foundit = 1
		    break
	    if not foundit :                    # If it's not already on the left hand list, append it
		left_list.append (right_val)
	return ",".join (left_list)

    if op == "&" :                             # Everything that's on both lists
	final = []                              # The final list
	for left_val in left_list :
	    foundit = 0
	    for j in right_list :
		if j == left_val :
		    foundit = 1
		    break
	    
	    if foundit :                        # If it's on both lists add it to the final list
		final.append (left_val)
	return ",".join (final)

    if op == "-" :                             # Subtract the right list from the left list
	final = [];                             # The final list
	for left_val in left_list :             # Everthing in the left list ...
	    foundit = 0
	    for j  in right_list :              # ... which is *not* in the right list
		if j == left_val :
		    foundit = 1
		    break
	    
	    if not foundit :		# If it's on on the left list but not the right list, append it to the result
		final.append(left_val)
        return ",".join (final)

    Whoops ("state_expr_operator_parser, line %(lineno)s: Don't understand operator '%(op)s'" % vars())
    
# ****************************************************************
# Generate the state functions

def state_expr_function_parser(lineno, func, args) :
    if debug:
        dprint ("state_expr_function_parser, line %(lineno)s, func='%(func)s', args='%(args)s'" % vars())
    found_states = []

    if func == "%all" :
	found_states = range (len(State.by_number))
    elif func == "%with" :
        found_states = sef_find_states_with_tags (lineno, args)
    elif func == "%add" :
        found_states = sef_add_sub_attribute (lineno, args, 1)
    elif func == "%remove" :
        found_states = sef_add_sub_attribute (lineno, args, 0)
    elif func == "%replace" :
        found_states = sef_add_sub_attribute (lineno, args, 2)
    elif func == "%without" :
	with_states = sef_find_states_with_tags (lineno, args)  # Find all the states _with_ the item, then we'll invert the list
	for s in xrange (len(State.by_number)) :
	    has_it = 0
	    for wstate in with_states:
		if wstate == s :
		    has_it = 1
		    break
	    if not  has_it :		# Wasn't on the 'with' list?
		found_states.append (s)	# Then it goes on the 'without' list+
	
	# Toggle is hard -- we need to parse an expression out which may contain commas
	# We don't.  We assume there will be no parens or brackets in the 'toggle' terms, and
	# that there won't be any commas outside brackets and parens in the 'expr' term, and
	# use that to split it up.
    elif func == "%toggle" :
        m = re.match (r"(.*?) , ([^\(\)\[\]]*)$", args, re.X)   # We expect an expression, a comma, and a comma separated simple list of state names
	if not m :  
	    Whoops ("state_expr_function_parser: line %(lineno)s: can't parse 'toggle' function arguments '%(args)s'\n" % vars())
        sexpr = wstrip (m.group(1))
        pair_str = wstrip (m.group(2))                  # Strip blanks
	state_str = pstrip (state_expression_parser (lineno, sexpr), "[]") # Strip brackets (which should be there)
	
	found_states = re.split (r"\s*,\s*", state_str);                   # The state list we'll return (not yet toggled)
	pairs = re.split (r"\s*,\s*", pair_str);                           # And split the pairs up
	if (len(pairs) % 2) :
	    Whoops ("state_expr_function_parser at line %(lineno)s: Toggle function needs a state list and a set of pairs; state names can't be odd" % vars())
	
	swapped_pairs = map (None, pairs)               # Start with a copy of pairs
        for i in range (0, len(pairs), 2) :
	    swapped_pairs[i] = pairs[i+1]      # Swap 'em in the swapped array
	    swapped_pairs[i+1] = pairs[i]
	
	for i in range (len(found_states)):      # Toggle the states we'll return
	    s = found_states[i]
	    for t in range (len(pairs)) :
		if s == pairs[t] :
		    found_states[i] = swapped_pairs[t]      # Switch for the toggled pair member
		    break;				    # Important that we break here!   Otherwise we'll toggle it right back!
	    
	# Just like toggle but we only go one way.
	# In other words, this is a substitute function.
    elif func == "%map" :
	m = re.match (r"(.*?) , ([^\(\)\[\]]*)$", args, re.X)         # We expect an expression, a comma, and a comma separated simple list of state names
        if not m:
	    Whoops ("state_expr_function_parser: line %(lineno)s: can't parse 'map' function arguments '%(args)s'\n" % vars())
	sexpr = wstrip (m.group(1))
        pair_str = wstrip (m.group(2))                  # Strip white space off the ends of the pair list -- it messes up the number parsing

	state_str = state_expression_parser (lineno, sexpr)
        state_str = pstrip (state_str, "[]")            # Strip brackets (which should be there)
	
        found_states = re.split (r"\s*,\s*", state_str)            # The state list we'll return (not yet mapped)
	pairs = re.split (r"\s*,\s*", pair_str)                    # And split the pairs up
	if len(pairs) % 2 :
	    Whoops ("state_expr_function_parser at line %(lineno)s: Map function needs a state list and a set of pairs; state names can't be odd\n")
	
	mapped_pairs = map (None, pairs)        # Initialize it as a copy of pairs.  We'll bash every other element.
        if debug:
            for i in xrange (len(pairs)):
                print >>sys.stderr, "   Pairs[%s]: %s Mapped_pairs[%s]: %s" % (i, pairs[i], i, mapped_pairs[i])
	for i in range (0, len(pairs), 2) :
	    mapped_pairs[i] =  (pairs[i+1])	# Map both members of each pair to the second member in the input pair (second member already set)
            if debug:
                print >>sys.stderr, "   Set mapped_pairs[%s] to %s" % (i, mapped_pairs[i])
	
        for i in xrange (len(found_states)):     # Map the states we'll return
            s = found_states[i]
	    for t in xrange (len(pairs)) :
		if (s == pairs[t]) :
		    found_states[i] = mapped_pairs[t]; # Switch for the mapped pair member
		    break;				    # Important that we break here not   Otherwise we'll toggle it right back not 
		
    else :
	Whoops ("state_expr_function_parser: line %(lineno)s: Don't understand function '%(func)s'" % vars())
    
    result = ",".join ("%s" % x for x in found_states) # Convert the numbers to strings so we can join them.  Python is picky about this.
    if debug:
        dprint ("state_expr_function_parser --> %(result)s" % vars())
    return "[" + result + "]"

# ****************************************************************
# Parse a state expression.  Surrounding brackets should already be stripped off.
# The line number argument is used in error messages.
#
# We have several special functions:
#
# %all()	-- All states known to us
# %with(tag1,tag2,...)	-- All states with inventory or attribute "tag1" and "tag2" and ... 
# %without(tags) -- Same but without any of the items
# %toggle(sexpr, State1a, State1b, State2a, State2b, ... ) -- Evaluate sexpr, and State1a, etc, then swap 1a<.1b, 2a<.2b, etc
#

def state_expression_parser(lineno, sexpr) :
    passed_expr = sexpr          # We may want this for error messages

    if debug:
        dprint ("state_expression_parser (at line %(lineno)s): '%(sexpr)s'" % vars())

    sexpr = pstrip (sexpr, "()")        # If there's a pair of parens around a paren-less expression, strip them
    sexpr = pstrip (sexpr, "[]")        # If there's a pair of brackets around a bracket-less expression, strip them
    
    if re.match (r"\s* \* \s*$", sexpr, re.X) :	# If it's just a "*" return the last one we expanded
	return "[" + G.last_state_expr + "]"
    
    # Function or parenthesized subexpression
    while 1:
        m = re.match (r"\s* (.*?) \s* (\%\w+)? \s*\(\s* ([^\(\)]*?) \s*\)\s* (.*?) \s*$", sexpr, re.X)
        if not m:
            break;
        lhs = m.group(1)
        fname = m.group(2)
        subex = m.group(3)
        rhs = m.group(4)

        if debug:
            dprint ("state_expression_parser:  Matched a function call: '%(lhs)s', '%(fname)s', '%(subex)s', '%(rhs)s'" % vars())
	
	if fname :                           # If it's a function call
	    middle = state_expr_function_parser (lineno, fname, subex)
        else:                                  # In this case it's just a subexpression
	    middle = state_expression_parser (lineno, subex)
	
	sexpr = "%(lhs)s%(middle)s%(rhs)s" % vars()

    # Bracketed subexpexpression
    while 1:
        m = re.match (r"\s* (.*?) \s* \[\s* ([^\[\]]*?) \s*\]\s* (.*?) \s*$", sexpr, re.X)
        if not m:
            break;
        lhs = m.group(1)
        subex = m.group(2)
        rhs = m.group(3)

        if debug:
            dprint ("state_expression_parser:  Matched a bracketed subexpression: '%(lhs)s', '%(subex)s', '%(rhs)s'" % vars())
	
        middle = state_expression_parser (lineno, subex)

        middle = pstrip (middle, "[]")          # Strip newly applied brackets so we don't fall down the recursion stairs
	sexpr = "%(lhs)s%(middle)s%(rhs)s" % vars()

    #
    # At this point all function calls have been evaluated, all subexpressions expanded.  There should be no
    # parens in the expression.  From here on we just split it on operator boundaries and combine the
    # halves.  We split one operator at a time in order to implement precedence.
    #

    while 1:
        # Note that the greedy '.*' on the left is needed to force left association
        m = re.match (r"(.*\S)? \s* ([\|]) \s* (.*?) \s*$", sexpr, re.X) # First try for 'or'  (lowest precedence)
        if not m:
            m = re.match (r"(.*\S)? \s* ([\&]) \s* (.*?) \s*$", sexpr, re.X) # Next try for 'and'
        if not m:
            m = re.match (r"(.*\S)? \s* ([\-\+]) \s* (.*?) \s*$", sexpr, re.X) # Finally try for '+' or '-' (highest precedence)
        if not m:
            break;

        lhs = m.group(1)
        op = m.group(2)
        rhs = m.group(3)
	lhs = state_expression_parser (lineno, lhs)
	rhs = state_expression_parser (lineno, rhs)
	sexpr = state_expr_operator_parser (lineno, lhs, op, rhs)
    
    final_list = []
    sexpr = re.sub (r"\s", "", sexpr)         # Delete all white space we haven't already removed

    for s in sexpr.split(","):
	if (re.match (r"\d*$", s)) :    	# If it's already a number, just use it.
	    sn = s
	else:                                   # Otherwise look it up
            try:
                sn = State.names[s].number
            except KeyError:
                sn = None

        if sn == None or sn == "" :
	    Whoops ("Line %s:  State %s isn't defined, splitting and substituting '%s'; passed '%s'" % (lineno, s, sexpr, passed_expr), "fnord")
	
        final_list.append(sn)
    
    str = ",".join ("%s" % x for x in final_list) # Convert the numbers to strings so we can join them.  Python is picky about this.
    return "[" + str + "]"                # Stick it in brackets when we return it

# ****************************************************************
# Given an expression of the form "[...]", strip the brackets and expand the
# expression into a comma separate list of state numbers.
#
# If it's not what we're expecting we just return it unchanged.
#
# This SAVES the last state expression expanded, and if it's called with "*", then it
# returns that.  That's how we make it possible to operate on the "X" expression when
# building the "Q" expression (the asterisk is used as a back-reference to the last one expanded).
#
# This is hotter than heck on a big file so we cache the result (unless it's got backrefs in it)
#

ese_cache = {}

def expand_state_expression (lineno, sexpr) :
    global ese_cache
    had_back_reference = "*" in sexpr  # If there's no asterisk in it, then it's safe to use the cache.
    if not had_back_reference:
        try:
            result = ese_cache[sexpr] # We just cache the input expression, raw.
            G.last_state_expr = result
            return result
        except KeyError:
            pass
        pass

    input = sexpr
    m = re.match (r"\s* \[ \s*  (.*?)  \s* \] \s*$", sexpr, re.X)
    if not m:
	return sexpr;           # No match -- it's probably already a list of state IDs or numbers

    sexpr = m.group(1)          # Discard the brackets and surrounding blanks
    sexpr = state_expression_parser (lineno, sexpr); # This normally returns a bracketed list.
    sexpr = pstrip (sexpr, "[]")        # If it's a pair of brackets around something, strip them.
    
    if debug:
        print >>sys.stderr, "expand_state_expression (line=%(lineno)s): %(input)s --> %(sexpr)s" % vars()
    
    G.last_state_expr = sexpr
    if not had_back_reference:        # If there's no asterisk in it, then save the result in the cache
        ese_cache[input] = sexpr
    return sexpr

# ****************************************************************
# Match a regular expression and save the match, as well as returning it.
# This is useful because we don't seem to be able to save it and test it in one statement.

def save_re_match (regex, string):
    G.m = regex.match (string)
    return G.m

def save_re_search (regex, string):
    G.m = regex.search (string)
    return G.m

def save_match (reg, string):
    re_with_dollar = reg + "$"
    m = re.match (re_with_dollar, string, re.X)
    if debug:
        print >>sys.stderr, "save_match: expression: '%s'; string: '%s'; m: '%s'" % (re_with_dollar, string, m)
        try:
            print >>sys.stderr, "    group 0: %s" % (m.group(0))
            print >>sys.stderr, "    group 1: %s" % (m.group(1))
        except:
            pass
    G.m = m
    return G.m

def save_search (reg, string):
    G.m = re.search (reg, string, re.X)
    return G.m

# ****************************************************************
# Compare a string against a string, or a string against a list of strings, in which
# case it returns true if the comparand matches anything on the list.
# It returns the value of the thing you passed in, or 0.
#
# We currently think all values must be numbers but if it turns out we're trying to pass
# random strings in we'll change the comparisons to work with strings.
#
# Pretty much all blanks are thrown away.  The parsing is recursive descent (surprise).
#
# Lexing of '=' is sloppy indeed; it actually accepts '=', '==', '===', ...
#
# Expressions we interpret:
#
# expr == expr		exprs evaluate to integers
# expr == [b,c,d...]	True if expr is in the set [...].
#			expr must be numeric.  Returns true if expr==b OR expr==c OR expr==d OR...
#			Note that the [] set construction does NOT NEST.  You get one level.  Be happy with it.
# expr {+,-.*,/,% expr The obvious.  Precedence is multiplication, addition, mod.
# ... (expr) ...	Evaluates the inner expr then recurses.  IOW they're parens, as you'd expect.
#
# ... <string>(expr) ... Evaluate "<string>(expr)" and substitute it.  This takes care of function calls.
#			Getting the balanced parens right is a pain.
#			The "<string>" can be alphanumeric plus underscores, and can start with a ".".
#			If macro calls start with "&" or "%" we will misparse them here.
#
#			THERE IS NO UNARY MINUS.  Deal with it.
#
# This is hot so we cache the result.  We hope that there's nothing mutable passed in.
#

eval_expr_cache = {}

def eval_expr(lineno, expr, parent_expr = None) :
    global eval_expr_cache
    try:
        cached = eval_expr_cache[expr]
        return cached
    except KeyError:
        saved_input_expr = expr # For use in caching our final result

    if parent_expr == None:
        parent_expr = "<null>"; 

    if debug:
        dprint ("eval_expr (line=%(lineno)s, expr='%(expr)s', parent expr='%(parent_expr)s'" % vars())

    expr = wstrip (expr)                                # Strip leading and trailing whitespace.  In the rest of this we assume it's been stripped.
    expr = pstrip (expr, "()")                          # Is this an expression in parens, without any embedded parens?
                                                        # If so just strip the parens (and whitespace).  This is needed to properly handle subexpressions.
    
    if re.match (r"! (.*)$", expr, re.X) :			# Leading " ! " is negation.  Precedence is wobbly here.
	return  not  eval_expr(lineno, m.group(1), expr)

    
    m = re.match (r"([0-9a-z_]+)$", expr, re.I | re.X)
    if m:	# A simple value.  If it's a number you'll get a number back.
	term = m.group(1)
	if not re.match (r"[0-9]+$", term):		# If it's not a number you'll get barf.
	    Whoops ("eval_expr:  Unexpected characters in term '%(term)s' at line %(lineno)s" % vars(), Mice())
        if debug:
            dprint ("eval_expr:  Simple value.")
	result = term

	# Nested parens and function calls.  This finds the first parenthesized subexpression which contains
	# at least one nested subexpression, pulls out the first nested subexpression, evaluates it, pastes
	# the result back in and then recurses to evaluate the whole thing.
	#
	# It also should pull out function calls nested in parens and evaluate them.
	#
	# Note that we'll see stuff like "... ( .. ) .. (a .. foo(bar)  ( .. )b .. ) ..."
	# This will identify the section from '(a' to ')b' and pull out the 'foo(bar)' as needing recursion.
	# We need to allow the extra parens between )b and the subexpression or we'll miss it when there
	# are two subexpressions within on nesting pair of parens
	#
    elif save_match (r"""( .* \( [^\)]*? )	# Leading paren for outer group  (NOTE the '?' shortest-match character)
			(			# Subexpression we're doing to evaluate
			   (?: [\%\&\.]? \w+)? \s*	# Optional function name
			   \( [^\(\)]+ \)	# Inner paren group, which may be an argument list
			)
			( .* \) .* ) """, expr) :
        lhs = G.m.group(1)
        middle = G.m.group(2)
        rhs = G.m.group(3)
        if debug:
            dprint ("eval_expr: Parenthesized expression, lexed out '%(lhs)s', '%(middle)s', '%(rhs)s'" % vars())

	middle = eval_expr (lineno, middle, expr);		# Evaluate the subexpression and recurse after pasting it back in
	result = eval_expr (lineno, "%(lhs)s%(middle)s%(rhs)s" % vars(), expr)

	# At this point we should have no nested parentheses.  There may be parens in the expression
	# but there should be just one level of them.
	#
	# We next evaluate our special built in functions, and any other function-like objects we may know about
	#
    elif save_match (r"\s*(.*)&scramble\s*\((.*?)\)(.*)", expr) : # SPECIAL -- "&scramble" returns a random value based on the input
        lhs = G.m.group(1)
        subex = G.m.group(2)
        rhs = G.m.group(3)
        if debug:
            dprint ("eval_expr: Scramble function call")
	subex = eval_expr(lineno, subex, expr)
	if not re.match (r"[0-9]*$", subex):
            Whoops ("eval_expr: Line %(lineno)s: '%(expr)s' has botched scramble argument -- evals to '%(subex)s'" % vars()); 
	rv = randoms[subex % len(randoms)]
	##rv = subex;			# Scrambling just made it all worse at 17, but it's OK at 13.
	result = eval_expr(lineno, "%(lhs)s%(rv)s%(rhs)s" % vars())

    elif save_match (r"\s*(.*)&perm\s*\((.*?),(.*?),(.*?)\)(.*)", expr):                 # SPECIAL -- "&perm" returns the next value in a shuffled sequence
        lhs = G.m.group(1)
        a1 = G.m.group(2)
        a2 = G.m.group(3)
        a3 = G.m.group(4)
        rhs = G.m.group(5)

        if debug:
            dprint ("eval_expr: Perm function call")
	a1 = eval_expr(lineno, a1, expr)
	a2 = eval_expr(lineno, a2, expr)
	a3 = eval_expr(lineno, a3, expr)
	rv = permutation_next (a1, int(a2), a3)
	result = eval_expr(lineno, "%(lhs)s%(rv)s%(rhs)s" % vars(), expr)

	# Set membership operator
	#
    elif save_match (r"""(?: (.*?)	# Expr on the left
			    \s* ( != | ==? ) \s*
			)?
			( \[ .*? \] )	# Set on the right
                        """, expr) :
	result = 0
        lhs = G.m.group(1)
        op = G.m.group(2)
        rhs = G.m.group(3)
	if not lhs:		# Bare state string?
	    result = expand_state_expression (lineno, rhs)
            result = pstrip (result, "[]")                      # Strip brackets which expand_state_expression wraps around stuff

        else:                                                   # In this case it's a state membership check, which will eval to a boolean
            if debug:
                dprint ("eval_expr: Set membership: '%(lhs)s', '%(op)s', '%(rhs)s'" % vars())
	    lhs = eval_expr (lineno, lhs, expr);                # Recurse in case the LHS is more complicated than a simple number
            if debug:
                dprint ("eval_expr (at lineno, expr='expr'):  Calling expand_state_expression with 'rhs'")
	    rhs = expand_state_expression (lineno, rhs)
            if debug:
                dprint ("eval_expr:  expand_state_expression returned '%(rhs)s'" % vars())
	    for r in rhs.split (",") :
		r = eval_expr (lineno, r, expr); # Recurse in case we think of a use for an expression in a set
		if (lhs == r) :
		    result = 1
		    break
	    if (op == "!=") :	     # If it's a  not =, negate the result
		result = not result
	    
	# We've evaluated all function calls.  If there are any parens left, they are just for grouping.
	# We evaluate them now.  We check first to see if there are any leftover function calls; if there
	# are we blew the parsing somewhere.
	#
    elif save_match (r""".*\w\s*			# End of a function name (the \w) and trailing blanks
		       \( .* \)				# Argument list
		       .* """, expr):			# End of a function name (the \w) and trailing blanks
	Whoops ("eval_expr line lineno:  We found a function call in '%(expr)s', parent expression '%(parent_expr)s'-- we thought we'd already substituted all of them." % vars())
    elif save_match (r"""(.*?)				# A parenthesized subexpression (the '?' character is optional here)
		       \( ([^\(\)]+) \)			# Subexpression bounded by parens, with no parens inside
		       (.*) """, expr):
        lhs = G.m.group(1)
        middle = G.m.group(2)
        rhs = G.m.group(3)
        if debug:
            dprint ("eval_expr: parenthesized subexpression: '%(lhs)s', '%(middle)s', '%(rhs)s'" % vars())
	middle = eval_expr (lineno, middle, expr);		# Evaluate the subexpression and recurse after pasting it back in
	result = eval_expr (lineno, "%(lhs)s%(middle)s%(rhs)s" % vars, expr)

	# Simple comparison
	#
    elif save_match (r"(.*?) \s* ( != | ==?) \s* (.*?)", expr) :	# Comparison of two simple values
        lhs = G.m.group(1)
        op = G.m.group(2)
        rhs = G.m.group(3)
	lhs = eval_expr (lineno, lhs, expr)
	rhs = eval_expr (lineno, rhs, expr)
	result = lhs == rhs
	if op == "!=" :
	    result = not result
	
	# Arithmetic stuff
	#
    elif save_match (r"\s*(.*?)(%)(.*?)\s*", expr) :	# Mod -- lowest precedence of the numeric operators
        lhs = G.m.group(1)
        op = G.m.group(2)
        rhs = G.m.group(3)
	lhs = eval_expr (lineno, lhs, expr)
	rhs = eval_expr (lineno, rhs, expr)
	result = lhs % rhs
    elif save_match (r"\s*(.*)([-+])(.*?)\s*", expr):	# Plus and minus.  Need greedy matching on the left to force left association.
        lhs = G.m.group(1)
        op = G.m.group(2)
        rhs = G.m.group(3)
	lhs = eval_expr (lineno, lhs, expr)
	rhs = eval_expr (lineno, rhs, expr)
	if (op == "+") :
	    result = lhs + rhs
        else :
	    result = lhs - rhs
	
    elif save_match (r"\s*(.*)([\*\/])(.*?)\s*", expr):	# Times and division -- highest precedence of the ones we interpret
        lhs = G.m.group(1)
        op = G.m.group(2)
        rhs = G.m.group(3)
	lhs = eval_expr (lineno, lhs, expr)
	rhs = eval_expr (lineno, rhs, expr)
	if (op == "\*") :
	    result = lhs * rhs
        else :
	    result = lhs / rhs
	
    else :
	Whoops ("eval_expr: near line %(lineno)s:  Don't understand '%(expr)s'\n"
                "   containing expr = '%(parent_expr)s'" % vars())
    
    if debug:
        print >>sys.stderr, "Eval_expr returning %(result)s" % vars(); 

    eval_expr_cache[saved_input_expr] = result # Cache it so we don't need to evaluate this one again
    return result

# ****************************************************************
# Define a macro on a single input line, with or without an argument list

def define_one_line_macro(inp) :
    if save_match (r"\s*(.*?)\((.*?)\)\s+(.*?)\s*", inp) :      # Macro with an argument list
        mname = G.m.group(1)
        al = G.m.group(2)
        to = G.m.group(3)
	if debug:
            print >>sys.stderr, "define_macro: Defining '%(mname)s' with args '%(al)s' and body '%(to)s'" % vars()
	G.simple_macros[mname] = to
	G.macro_args[mname] = al.split (",")

    elif save_match (r"\s*(.*?)\s+(.*?)\s*", inp):              # Macro with no argument list
        mname = G.m.group(1)
        to = G.m.group(2)
	if debug:
            print >>sys.stderr, "define_macro: Defining '%(mname)s' with body '%(to)s'" % vars()
	G.simple_macros[mname] = to
    else :
	Whoops ("define_macro:  Didn't understand '%(inp)s'" % vars())

# ****************************************************************
# Do the work of actually defining a state, now that we've gathered all the data for it.

def pp_define_state (lineno) :
    try:
        new_state = State.names[G.pp_state_name] # Find the state
    except KeyError:
        new_state = State (G.pp_state_name) # Allocate if necessary
    pp_state_number = new_state.number # And get the number for the state
    in_header = 0
    htext = ""
                                # Footer text
    share_ilist = 1;            # If there are no changes to the ilist we should share it
    for l in G.pp_text.split ("\n") :
	if save_match (r"\s* h: (.*?) \s*", l):
	    share_ilist = 0;    # Any change to the inventory list => we can't share it
            if debug:
                dprint ("process_defs: Header text line '%(l)s'" % vars())
	    in_header = 1
	    htext = G.m.group(1)
        elif save_match (r"\s* f: (.*?) \s", l) :
	    share_ilist = 0; # Any change to the inventory list => we can't share it
            if debug:
                dprint ("process_defs: Footer text line '%(l)s'" % vars())
	    in_header = 0
	    ftext = 1
        elif save_match (r"\s* \+: \s* (.*?) \s*\| (.*?) \s*", l) :
	    share_ilist = 0; # Any change to the inventory list => we can't share it
	    in_header = 0
            tag = G.m.group(1)
            item = G.m.group(2)
	    if (tag != None and re.match (r"\s*$", tag)): # Blank tag?
                tag = None; 
            if (item and re.match (r"\s*$", item)) :
                item = None;
            if debug:
                dprint ("process_defs: Inventory addition line '%(l)s'" % vars())
            if tag == None:
                Whoops ("pp_define_state: at line %s:  All inventory items must have tags!" % lineno)
            if debug:
                dprint ("proprocess_defs: Adding inventory item; tag='%(tag)s'" % vars())
            if item :	# Do we have an inventory string as well as a tag?
                if debug:
                    dprint ("proprocess_defs: Adding tagged inventory item '%(item)s'" % vars())
                try:
                    old = G.tag_to_inventory_item [tag]
                    if old != item :
                        Whoops ("At %(lineno)s:  Tag '%(tag)s' was '%(old)s', redefined to be '%(item)s'\n" % vars())
                except KeyError:
                    G.tag_to_inventory_item[tag] = item
	    if not item:
		item = G.tag_to_inventory_item[tag]
		if not item:
		    Whoops ("Line %(lineno)s:  Inventory item tag '%(tag)s' without an item, and no previous tag definition\n" % vars())
	    
	    G.pp_new_inventory.append (tag)
	    G.pp_new_inventory_flags[tag] = 1

        elif save_match (r"\s* \-:  \s* (.*?) \s*\| (.*?) \s*", l):
	    share_ilist = 0; # Any change to the inventory list => we can't share it
	    in_header = 0
            tag = G.m.group(1)
            try:
		G.tag_to_inventory_item[tag]
            except KeyError:
                Whoops ("Line %(lineno)s: '%(l)s': Can't figure out what you're trying to delete" % vars())
	    
            G.pp_new_inventory_flags[tag] = -1

        elif save_match (r"\s* \+a: \s*  (.*?) \s*\| (.*?) \s*", l) : # Attribute line
	    in_header = 0
            tag = G.m.group(1)
            item = G.m.group(2)
	    if tag and re.match(r"\s*$", tag):
                tag = None;
	    if item and re.match (r"\s*$", item):
                item = None; 
            if tag == None:
                Whoops ("pp_define_state: at line %s:  All attributes must have tags!" % lineno)
            if item :                       # Do we have an inventory string?
                try:
                    old = G.tag_to_attribute[tag]
                    if old != item:
                        Whoops ("At %(lineno)s:  Attribute tag '%(tag)s' was '%(old)s', redefined to be '%(item)s'\n" % vars())
                except KeyError:
                    G.tag_to_attribute[tag] = item
		
            if not item:
                try:
                    item = G.tag_to_attribute[tag]
                except KeyError:
		    G.tag_to_attribute[tag] = "-" # It really doesn't matter -- we don't need a long description of the attributes
		
	    G.pp_new_attributes.append (tag)
	    G.pp_new_attribute_flags[tag] = 1

        elif save_match (r"\s* \-a: \s* (.*?) \s*\| (.*?) \s*", l):
	    in_header = 0
            tag = G.m.group(1)
            item = G.m.group(2)
	    if not item:
		item = G.tag_to_attribute[tag]
		if not item:
		    Whoops ("Line %(lineno)s: '%(l)s': Can't figure out what you're trying to delete" % vars())
	    
	    G.pp_new_attribute_flags[tag] = -1

        elif in_header :
	    htext += "\n%(l)s" % vars()
        else :
	    Whoops ("Parsing state (line=%(lineno)s): Didn't understand '%(l)s'" % vars())
	
    if G.pp_base_state_name :
        old_state = State.names[G.pp_base_state_name] # Get the old state number
        osn = old_state.number
        try:
            oin = G.shared_ilist_for_state[int(osn)]   # The old inventory number
        except KeyError:                # If it isn't sharing, use its state number
	    oin = osn

        if share_ilist:                 # Are we sharing the base state's list?
	    G.shared_ilist_for_state[int(pp_state_number)] = oin       # Share the one it shared!

        old_inventory = old_state.ilist         # But use the actual state's lists to diff from
	if not old_inventory :
	    Whoops ("Line %s:  Building state %s from %s:  No old inventory list found\n" % (lineno, G.pp_state_name, G.pp_base_state_name))
	
	G.pp_new_inventory[0] = old_inventory[0]; # Default to the header text from the old inventory list (we'll overwrite this later with htext)
	for i in xrange(1, len(old_inventory)) :        # Any old inventory item which hasn't already been added or deleted, we should add.
	    tag = old_inventory[i]
            try:
                f = G.pp_new_inventory_flags[tag]
            except KeyError:
                f = None
            if not f:
		G.pp_new_inventory.append (tag)

        old_attributes = old_state.attributes
	if old_attributes == None :
	    Whoops ("Line %s:  Building state %s from %s:  No old attribute list found\n" % (lineno, G.pp_state_name, G.pp_base_state_name))
	
	for i in xrange (len(old_attributes)) :         # Any old inventory item which hasn't already been added or deleted, we should add.
	    tag = old_attributes[i]
            try:
                naf = G.pp_new_attribute_flags[tag]
            except KeyError:
                naf = None
	    if not naf :
		G.pp_new_attributes.append (tag)
	    
    elif share_ilist :
	Whoops ("State %s, %s: We're supposed to share the ilist but there's no base state\n" % (G.pp_state_name, pp_state_number))
    
    if htext :                          # If we have new header text, pick it up now (overwrite any old header)
        htag = "HDRxxxxxxx%s" % G.header_count
        G.tag_to_inventory_item[htag] = htext
        G.header_count += 1
	G.pp_new_inventory[0] = htag

    if debug:
        i0 = G.pp_new_inventory[0]
        ic = len(G.pp_new_inventory)
        print >>sys.stderr, "Set state %s inventory to %s elements, first is %s" % (G.pp_state_name, ic, i0)

    if 1 or not share_ilist :
	if debug:
            print >>sys.stderr, "State %s, number %s, got its own inventory list (but may not get a room for it)" % (G.pp_state_name, pp_state_number)
	new_state.ilist = G.pp_new_inventory   # Save the list if we're not sharing.
	new_state.attributes = G.pp_new_attributes
    elif debug :
        iln = G.shared_ilist_for_state[int(pp_state_number)]
	print >>sys.stderr, "State %s, number %s, is sharing ilist %s" % (G.pp_state_name, pp_state_number, iln)
    
# ****************************************************************
# Do the work of defining extension attributes for a state, now that we've gathered all the data for them.

def pp_define_attributes (lineno) :
    try:
        new_state = State.names[G.pp_state_name] # Find the state
    except KeyError:
        Whoops ("define_attributes near line %s:  State %s isn't defined" % (lineno, G.pp_state_name))

    pp_state_number = new_state.number # And get the number for the state

    defining_relations = 0            # Set this when we start defining relations -- can't define more attributes at that point
    defining_nonequiv_relations = 0
    for l in G.pp_text.split ("\n") :
	if save_match (r"\s* h: (.*?) \s*", l):
            Whoops ("pp_define_attributes:  You can't put a header line on the attributes: '%s' (not yet, anyway)" % l)
        elif not defining_relations and save_match (r"\s* \+: \s* (.*?) \s*\| (.*?) \s*", l) :
            tag = G.m.group(1)
            item = G.m.group(2)
	    if (tag != None and re.match (r"\s*$", tag)): # Blank tag?
                tag = None; 
            if (item and re.match (r"\s*$", item)) :
                item = None;
            if debug:
                dprint ("define_attributes: Inventory addition line '%(l)s'" % vars())
            if tag == None:
                Whoops ("pp_define_attributes, at line %s: All inventory items must have tags" % lineno)
            if debug:
                dprint ("proprocess_defs: Adding inventory item; tag='%(tag)s'" % vars())
            if item :	# Do we have an inventory string?
                if debug:
                    dprint ("proprocess_defs: Adding tagged inventory item '%(item)s'" % vars())
                try:
                    old = G.tag_to_inventory_item [tag]
                    if old != item :
                        Whoops ("At %(lineno)s:  Tag '%(tag)s' was '%(old)s', redefined to be '%(item)s'\n" % vars())
                except KeyError:
                    G.tag_to_inventory_item[tag] = item
            else:
                try:
                    item = G.tag_to_inventory_item[tag]
                except KeyError:
		    Whoops ("Line %(lineno)s:  Inventory item tag '%(tag)s' without an item, and no previous tag definition\n" % vars())

	    G.pp_new_inventory.append (tag)

        elif not defining_relations and  save_match (r"\s* \+a: \s* (.*?) \s*\| (.*?) \s*", l) : # Attribute line
            tag = G.m.group(1)
            item = G.m.group(2)
	    if tag and re.match(r"\s*$", tag):
                tag = None;
	    if item and re.match (r"\s*$", item):
                item = None; 
            if tag == None:
                Whoops ("pp_define_attributes, at line %s: All attributes must have tags" % lineno)
            if item :                       # Do we have an inventory string?
                try:
                    old = G.tag_to_attribute[tag]
                    if old != item:
                        Whoops ("At %(lineno)s:  Attribute tag '%(tag)s' was '%(old)s', redefined to be '%(item)s'\n" % vars())
                except KeyError:
                    G.tag_to_attribute[tag] = item
            else:
                try:
                    item = G.tag_to_attribute[tag]
                except KeyError:
		    G.tag_to_attribute[tag] = "-" # It really doesn't matter -- we don't need a long description of the attributes

	    G.pp_new_attributes.append (tag)

        #
        # Specify some relations.  This is important to cut down on 'intermediate bulge' when
        # we're building the states, and to make it a little easier to figure out if
        # something's wrong.  Getting thousands of orphaned rooms is not helpful.
        #
        elif save_match (r"\s*  r: \s* (.*?) \s*", l) :
            rel = G.m.group(1)
            #
            # This relation is special:  LHS and RHS are equivalent.
            # We strip the LHS out of the lists; we'll put it back later.
            #
            # NOTE NOTE NOTE NOTE:  This sets a NEW attribute or inventory item as being
            # equivalent to SOME other attribute or inventory item.  The one to which it's
            # equived can be new OR OLD.  So, to specify an attribute or item which should
            # be added to EVERY NEWLY CREATED STATE, you can say "r: New_attr = Old_attr" where
            # "Old_attr" is an attribute in the base state. (Whether it works to equiv a
            # new attribute to an old inventory item, or vice versa, is not clear -- hasn't
            # been tested.)
            #
            # All other relations must be defined solely between NEW inventory and attribute items.
            #
            if save_match (r"\s* (\w*?) \s*\=\s* (\w*) \s*", rel):
                if defining_nonequiv_relations:
                    Whoops ("At line %s:  Equivalence relation defined *after* a non-equivalence relation: '%s'" % (lineno, l))
                lhs = G.m.group(1)
                rhs = G.m.group(2)

                equiv = new_state.equiv_inventory # We'll add the equivalence to this list
                try:
                    G.pp_new_inventory.remove(lhs)
                except ValueError:
                    equiv = new_state.equiv_attribute # No, actually, we'll add the equivalence to *this* list
                    try:
                        G.pp_new_attributes.remove(lhs)
                    except ValueError:
                        Whoops ("At line %s: Equiving attribute %s but it doesn't seem to be known; relation: %s" % (lineno, lhs, rel))
                try:
                    eq_list = equiv[rhs]
                except:
                    eq_list = []
                    equiv[rhs] = eq_list

                eq_list.append (lhs) # Stick it on the list of things equivalent to the RHS
                continue

            if not defining_relations:
                defining_relations = 1
                defining_nonequiv_relations = 1
                new_state.extra_ilist = G.pp_new_inventory[1:]   # Save the list if we're not sharing.  Drop first item, which is the header.
                new_state.extra_attributes = G.pp_new_attributes
                G.pp_new_inventory = []
                G.pp_new_attributes = []
                new_state.establish_attribute_numbers()
                pass
            new_state.relations.append (new_state.define_one_relation (lineno, rel))

        else :
	    Whoops ("Parsing attributes (line=%(lineno)s): Didn't understand '%(l)s'" % vars())
        pass

    if debug:
        if len(G.pp_new_inventory) > 0:
            i0 = G.pp_new_inventory[0]
        else:
            i0 = "<missing>"
        ic = len(G.pp_new_inventory)
        print >>sys.stderr, "New attributes for state %s: %s elements, first is %s" % (G.pp_state_name, ic, i0)

    if G.pp_new_inventory:
        new_state.extra_ilist = G.pp_new_inventory[1:]   # Save the list if we're not sharing.  Drop first item, which is the header.
    if G.pp_new_attributes:
        new_state.extra_attributes = G.pp_new_attributes
    pass

# ****************************************************************
# Preprocess macro definitions in one line.  Returns true if it eats the line.

def preprocess_defs(lineno, line) :
    if debug:
        print >>sys.stderr, "preprocess_defs(line %(lineno)s) processing '%(line)s'" % vars()

    if G.pp_defining :
	if re.match (r"\#defend\b", line):    # If it's the end of the def, record it.
	    if G.pp_defining_state or G.pp_defining_attributes :
		Whoops ("preprocess_defs (line=%(lineno)s): Defining a state or attributes, encountered a defend: '%(line)s'\n" % vars())
	    G.simple_macros[G.pp_name] = G.pp_text
	    G.pp_name = None
	    G.pp_text = ""
	    G.pp_defining = 0
        elif re.match (r"\#endstate\b", line) :
	    if not G.pp_defining_state:
		Whoops ("preprocess_defs (line=%(lineno)s): Not defining a state, encountered an endstate: '%(line)s'\n" % vars())
	    pp_define_state (lineno)
	    G.pp_text = ""
	    G.pp_defining = 0
	    G.pp_defining_state = 0
	    G.pp_state_name = None
	    G.pp_base_state_name = None
	    G.pp_new_inventory = [""]
	    G.pp_new_attributes = []
	    G.pp_new_inventory_flags = {}
	    G.pp_new_attribute_flags = {}
        elif re.match (r"\#endattributes\b", line) :
	    if not G.pp_defining_attributes:
		Whoops ("preprocess_defs (line=%(lineno)s): Not defining attributes, encountered an endattributes: '%(line)s'\n" % vars())
	    pp_define_attributes (lineno)
	    G.pp_text = ""
	    G.pp_defining = 0
	    G.pp_defining_attributes = 0
	    G.pp_state_name = None
	    G.pp_base_state_name = None
	    G.pp_new_inventory = [""]
	    G.pp_new_attributes = []
	    G.pp_new_inventory_flags = {}
	    G.pp_new_attribute_flags = {}
        elif re.match (r"\#", line) and \
                (not re.match (r"\#(?:end)?if", line) and not re.match (r"\#\?", line) and not re.match (r"\#else", line)) : # We skip the conditionals for now.
            Whoops ("Line %(lineno)s: Unexpected preprocessor directive while defining a macro or state: '%(line)s'\n" % vars())
        else :
	    if G.pp_text != "" :
		G.pp_text += "\n"
            G.pp_text += line
	return 1

    elif re.match (r"\#defbegin", line):
	if save_match (r"\#defbegin\s+(\S*?)\((.*?)\)\s*", line) : # Macro with an argument list
            G.pp_name = G.m.group(1)
            al = G.m.group(2)
	    if debug:
                print >>sys.stderr, "preprocess_defs: Defining macro '%s' with args '%s'" % (G.pp_name, al)
	    G.macro_args[G.pp_name] = al.split(",")
	    G.pp_defining = 1
        elif save_match (r"\#defbegin\s+([^\(\)]*?)\s*", line) : # Macro without an argument list.
            G.pp_name = G.m.group(1)
	    if debug:
                print >>sys.stderr, "preprocess_defs: Defining macro '%s' with no arguments" % (G.pp_name)
            G.pp_defining = 1
        else :
	    Whoops ("preprocess_defs: Line %(lineno)s: Didn't understand line %(line\n)s" % vars())
	return 1

    elif save_match (r"\#define\s*(.*)", line) :
	define_one_line_macro (G.m.group(1))
	return 1

    elif save_match (r"""\#state\s* \(		# State begin tag
		      \s* (.*?) \s*		# State name
		      (?:,\s* (.*?) \s*)?	# Optional name of state on which to build it
		       \)\s*
                       """, line):
        G.pp_state_name = G.m.group(1)
        G.pp_base_state_name = G.m.group(2)
	G.pp_defining_state = 1
	G.pp_defining = 1

        if G.pp_state_name[0] != "%":
            Whoops ("State definition at line %s:  State name %s doesn't begin with '\%'" % (lineno, G.pp_state_name))
    elif save_match (r"""\#attributes\s* \(	# Attributes begin tag
		      \s* (.*?) \s*		# State name
		       \)\s*
                       """, line):
        G.pp_state_name = G.m.group(1)
        G.pp_base_state_name = None
	G.pp_defining_attributes = 1
	G.pp_defining = 1
    elif save_match (r"""\#instantiate\s* \(	# Attributes instantiation tag
		      \s* (.*?) \s*		# State name
		       \)\s*
                       """, line):
        sn = G.m.group(1)
        try:
            s = State.names[G.m.group(1)]
        except KeyError:
            Whoops ("Instantiating state attributes: state '%s' is not defined at line %s, '%s'" % (G.m.group(1), lineno, line))
        s.instantiate_attributes (lineno)
    elif re.match (r"\#(?:end)?if", line) or re.match (r"\#\?", line) or re.match (r"\#else", line): # We skip the conditionals for now+
	return 0
    elif re.match (r"\#[a-zA-Z]", line):
	Whoops ("preprocess_defs:  line %(lineno)s: Didn't understand '%(line)s'\n" % vars())

    return 0

# ****************************************************************
# Expand all macros in an array of lines and return the newly expanded
# array along with a flag indicating whether we did anything
#

def expand_macros_once(lineno,input) :
    expanded_something = 0
    output = []

    for l in input :                             # In Perl we didn't use "foreach <elt> (array)" 'cause it gives us a live copy of the line, I think
	expanded_this_line = 0
	#
	# Expand the simple ones...
	#
	for k in G.simple_macros.keys() :
            key_escaped = re.escape (k)
            ms = "%s\\b" % (key_escaped)
	    if not re.search (ms, l): 		# We do NOT require a word break BEFORE the macro, only after
                continue
	    expanded_this_line = 1
	    v = G.simple_macros[k]
            try:
                args = G.macro_args[k]
            except KeyError:
                args = None
	    if args :
                match_with_args = key_escaped + r"\s*\((.*?)\)"
                m = re.search (match_with_args, l)
		if not m :
		    Whoops ("Argument list missing, expanding macro '%(k)s' near line %(lineno)s" % vars())
                al = m.group(1)
		actuals = al.split(",")
		formals = args
		if len(formals) != len(actuals):
                    Whoops ("expand_macro_once(line ~ %(lineno)s) Wrong number of arguments for '%(k)s', expanding '%(l)s'\n" % vars()); 

                for j in xrange (len(formals)) :        # As with the macro defs, we don't require a word break *before* the arg.
		    formal = re.escape (formals[j])
		    actual = actuals[j]
                    v = re.sub (formal + r"\b", actual, v)      # Substitute into the macro definition

                l = re.sub (match_with_args, v, l, 1)   # JUST ONE SUBSTITUTION here, as we need to re-eval the actuals every time
            else :
		l = re.sub (ms, v, l)                   # This should do all occurrences on the line
	
        if re.search (r"\%", l): # If there's something that looks like a state name on the line, try to subst it
            for k in State.names.keys() : # There may be a lot of states so we don't do this unless we must
                key_escaped = re.escape (k)
                if not re.search (key_escaped + r"\b", l): 		# We require a word boudary at the END of the state name, not the beginning not  not 
                    continue;
                v = "%s" % (State.names[k].number)               # We need this to be a string, not an integer (gotte be a better way)
                l = re.sub (key_escaped + r"\b", v, l)

	# We may have a multiline expansion.  To make sure things work right we need to split it into lines.
	if not expanded_this_line: # If we didn't expand it just put it on the output intact
	    output.append(l)	     # If we try to split it we kill blank lines.
        else :
	    expanded_something = 1; # If we expanded it, split it into lines
            lines = l.split("\n")
	    for j in xrange(len(lines)) :
		piece = lines[j]
                ### Don't know if this is a problem with Python, or only with Perl
		###if (j  not = #lines || piece ne "") { # Do NOT push the 'extra' blank line we get courtesy of split
                output.append(piece)
		
    return (expanded_something, output)

# ****************************************************************
# Expand all macros in an array of lines repeatedly until it settles down

def expand_macros(lineno,lines) :
    expanded_something = 1
    while (expanded_something) :
	expanded_something = 0
        res = expand_macros_once (lineno, lines)
	expanded_something = res[0]
        lines = res[1]
    
    return lines

# ****************************************************************
# Handle conditionals.  We do this very late so all expansions have already
# been done, and we can, consequently, conditionalize on the current state.
# We do this a line at a time because the room parser is doing its own substitutions
# a line at a time, and we need to get hold of the lines *after* that substitution
# has been done, since that's what puts the current state values into the text.
#
# An if/else/endif can span macro boundaries, which is weird; it's because we
# process the conditionals after macro expansion.
#
# Returns 1 if it eats the line, 0 otherwise.
#
# Nesting is the obvious, with else/endif associated with the nearest if.
#

#
# We're not called from a recursive descent parser here and we have no convenient way
# to recurse so we're just going to handle the nesting manually
#
def handle_conditions(lineno, line) :
    result = 0
    if debug:
        print >>sys.stderr, "handle_conditions(lineno=%(lineno)s): '%(line)s'" % vars()

    if re.match (r"\#if\b", line) and G.pp_inside_if:	# Whoops -- recursion time!
	G.saved_pp_start_line.append (G.pp_start_line)
	G.saved_pp_condition_skipping.append (G.pp_condition_skipping)
	G.saved_pp_inside_if.append (G.pp_inside_if)
	G.saved_pp_skip_else.append (G.pp_skip_else)
	G.saved_pp_skip_whole_thing.append (G.pp_skip_whole_thing)

	if G.pp_condition_skipping:
            G.pp_skip_whole_thing = 1  # If not skipping leave this set to whatever it was set to
        G.pp_start_line = None
	G.pp_condition_skipping = None
	G.pp_inside_if = None
	G.pp_skip_else = None

	G.pp_nest_level += 1

    if not G.pp_inside_if :
	if re.match (r"\#else", line) or re.match (r"\#endif", line):
	    Whoops ("WHOOPS -- line %(lineno)s, text '%(line)s': else or endif not inside an if\n" % vars())
	
	if re.match (r"\#if\b", line) :
	    if debug:
                print >>sys.stderr, "Found 'if' at line %(lineno)s" % vars()
            G.pp_start_line = lineno
	    result = 1
	    G.pp_inside_if = 1
	    G.pp_skip_else = 1
	    if save_match (r"\#if\s*\((.*)\)\s*", line) :
                if debug:
                    print >>sys.stderr, "G.m: %s" % G.m
                    print >>sys.stderr, "Groups in the match:"
                    for k in range(10):
                        try:
                            g = G.m.group(k)
                            print >>sys.stderr, "   %s" % g
                        except:
                            print >>sys.stderr, " Exception at index %s" % k
                            break
		ok = eval_expr (lineno,G.m.group(1))
		if not ok:
		    G.pp_condition_skipping = 1
		    G.pp_skip_else = 0; # We will NOT skip the else
            else :
		Whoops ("handle_conditions: Somewhere around %(lineno)s: Didn't understand '%(line)s'\n" % vars(), Mice())
	
    elif re.match (r"\#elseif\b", line):
	result = 1
	if save_match (r"\#elseif\s*\((.*)\)\s*", line):
            else_expr = G.m.group(1)
	    G.pp_condition_skipping = G.pp_skip_else;       # If we had an accepted if, then we're skipping all else clauses
	    if not G.pp_condition_skipping:               # Not just skipping -> check the condition
		G.pp_condition_skipping = not eval_expr (lineno,else_expr)
	    
	    if not G.pp_condition_skipping:
		G.pp_skip_else = 1; # We want to skip any further 'else' clauses in this if, if we don't skip this one.
	    
        else :
	    Whoops ("handle_conditions: Somewhere around %(lineno)s: Didn't understand '%(line)s'\n" % vars(), Mice())
	
    elif re.match (r"\#else\b", line):
	G.pp_condition_skipping = G.pp_skip_else
	result = 1
    elif re.match(r"\#endif\b", line):
	G.pp_condition_skipping = 0
	G.pp_skip_else = 0
	G.pp_inside_if = 0
	result = 1

	if (G.pp_nest_level) :			# Time to pop the old state back
	    G.pp_start_line = G.saved_pp_start_line.pop()
	    G.pp_condition_skipping = G.saved_pp_condition_skipping.pop()
	    G.pp_inside_if = G.saved_pp_inside_if.pop()
	    G.pp_skip_else = G.saved_pp_skip_else.pop()
	    G.pp_skip_whole_thing = G.saved_pp_skip_whole_thing.pop()

	    G.pp_nest_level -= 1
	
    elif (G.pp_condition_skipping or G.pp_skip_whole_thing) :
	result = 1
    else :			# In this case we're in an 'if' but we're not skipping it.
	result = 0
    
    return result

# ****************************************************************
# Call as: parse_room_worker (sub_for, to_term, room_p, room_start_line, show)
#
# We have one (or more) builtin macro(s) as well.  We expand them here after all
# other expansions are done, because we want to be able to look up room names, and
# the room names are built from the state tag which is substituted in here when
# we instantiate the room.
#
# .RINDEX (room-name)  ... Expands into the (final, optimized) room number for "room-name"
#		     Note that if "room-name" is written as "Smoof-fooble-X" where "X" is
#		     the current room state, "X" must be substituted BEFORE .RINDEX is
#		     expanded, or it won't work.
#


##my @room

##pf_roomno = 0

prmw_regex_room_name = re.compile (r"""
			            ( [a-z0-9-_]+ )
                                    $
                                    """, re.X | re.I)

prmw_regex_macro_check = re.compile (r"\.[A-Z]")

prmw_regex_lex_link = re.compile (r"""(.*?) \[\[\.\.\/		# Lex out a link.  Start with stuff on the line before the link
                                  (.*?) \| (.*)                 # The pipe link itself ($2 and $3)
                                  \]\] (.*)                     # Trailing stuff on the line
                                  $
                                  """, re.X)

def parse_room_worker(room, room_start_line, show, string_pairs) :
    ##global pf_roomno
    lineno = room_start_line
    get_room_name = 1
    parsed_room = None

    if debug :
        fs = len(string_pairs)
	print >>sys.stderr, "parse_room_worker called for room at line %(room_start_line)s with %(fs)s translations" % vars()
	for p in string_pairs :
	    print >>sys.stderr, "   %s --> %s" % (p[0], p[1])
    
    force_keep = None
    for line in room:
	lineno += 1
        if line == ".KEEP":      # This must be all there is on the line
            force_keep = 1
            if parsed_room:
                parsed_room.used = 1
            continue

        for p in string_pairs :                        # If we've got some substitutions to perform
            f = re.escape (p[0])
            t = p[1]
            line = re.sub (r"\b" + f + r"\b", t, line)     # For the state tags we require a word break before AND after
	
        if handle_conditions (lineno,line) :
	    continue
	
	if get_room_name :
	    if re.search (r"=", line):          # If there's an equals, it's a substitution spec and not a room name.
		continue
	    
	    get_room_name = 0
            ##m = re.match (r"""(?: \|\{\{ )?
            ##		      ( [a-z0-9-_]+ )
	    ##		      (?: \}\}= )?""", line, re.X | re.I);
            m = prmw_regex_room_name.match (line)
	    if not m:
		if re.search (r"Dummy", line) :    # This is the dummy room at the end.  Discard it.
		    get_room_name = 0
		    continue
		Whoops ("Room name not found where expected on line %(lineno)s" % vars(), Mice())
	    
	    rn = m.group(1)
	    if not show :
                new_num = Room.alloc_roomno()
                if debug:
                    print >>sys.stderr, "Setting %s to number %s" % (rn, new_num)
                try:
                    parsed_room = Output_Room.orooms[rn]
		    Whoops ("Room %s at line %s was already defined at line %s" % (rn, room_start_line, parsed_room.lineno), Mice())
                except KeyError:
                    parsed_room = Output_Room (rn, room_start_line, new_num)
                    if force_keep:              # If we're forcing this to be kept, set the used bit
                        parsed_room.used = 1
            else :
                new_num = Room.alloc_roomno()
                try:
                    parsed_room = Output_Room.orooms [rn]
                    if int(new_num) != parsed_room.number :
                        Whoops ("On show pass, and room number of '%s' changed from %s to %s" % (rn, parsed_room.number, new_num), Mice())
                except KeyError:
		    Whoops ("On show pass, and room %(rn)s not defined when we got to it\n" % vars(), Mice())

            parsed_room.otext = ""
            if show:
                if optimize:
                    parsed_room.otext += "|%s=\n" % (Output_Room.orooms[rn].ordinal)
                    parsed_room.otext += "<!-- Room '%s' -->\n" % rn
                else :
                    parsed_room.otext += "|{{TheStenchIndex|%s}}=\n" % rn
        else :
	    #
	    # Expand our builtin macro(s).  They (or it) are wired in, hard coded, right here.
	    #
            if prmw_regex_macro_check.search (line): # Only if there's something that looks like a macro on the line!
                rname_macro = r"\.ILIST"		# Inventory list for a state.  Parens mis-parse, so just give it a simple state name
                ms = rname_macro + r"\s*\((.*?)\)"
                while save_search (ms, line) :
                    s_str = string.strip(G.m.group(1))
                    if not re.match(r"\d*$", s_str):
                        Whoops ("At line %s: .ILIST(%s) not understood" % (lineno, s_str), Mice())
                    snum = int(s_str)
                    try:
                        inum = G.shared_ilist_for_state[snum]
                    except KeyError:
                        inum = snum
                    line = re.sub (ms, "%s" % inum, line)

                rname_macro = r"\.PERM"		# Permutation-next operation for maze building
                ms = rname_macro + r"\s*\(\s*(.*?)\s*,\s*(.*?)\s*,\s*(.*?)\s*\)"
                while save_search (ms, line) :
                    a1 = G.m.group(1)
                    a2 = G.m.group(2)
                    a3 = G.m.group(3)
                    inum = permutation_next(a1, int(a2), a3)
                    if inum == None:
                        inum = a3
                    line = re.sub (ms, inum, line)

                rname_macro = r"\.EXPR"		# Arbitrary expression.  Parens probably mis-parse so this is kinda useless.
                ms = rname_macro + r"\s*\((.*)\)"
                while save_search (ms, line) :
                    subex = G.m.group(1)
                    inum = eval_expr(lineno,subex)
                    if inum == None:
                        inum = subex

                    line = re.sub (ms, inum, line)

                rname_macro = r"\.RINDEX"		# That's "Room Index".  Parens mis-parse.
                ms = rname_macro + r"\s*\((.*?)\)"
                while save_search (ms, line) :
                    rname = G.m.group(1)
                    ##Input_Room.put_on_work_queue (rname) # Note that we need to process this one
                    try:
                        rnum = Output_Room.orooms[rname].ordinal
                        parsed_room.links[rname] = 1	# Macro argument counts as a link to the room
                    except KeyError:
                        parsed_room.bad_links[rname] = 1
                        rnum = -1
                        if show: 
                            Whoops ("expand_macros_once, at line %(lineno)s: %(rname_macro)s(%(rname)s): Room not defined" % vars(), Mice()); 
                    line = re.sub (ms, "%s" % rnum, line)

                rname_macro = r"\.ERROR\b"		# Barf.
                if re.search (rname_macro, line):
                    Whoops (".ERROR macro found at %(lineno\n)s" % vars())
	    
	    if save_re_match (prmw_regex_lex_link, line) : # Lex out a link.
                line_prefix = G.m.group(1)
                rname = G.m.group(2)
                link_text = G.m.group(3)
                line_suffix = G.m.group(4)
                ##Input_Room.put_on_work_queue (rname) # Note that we need to process this one

		if 1 or show:
                    try:
                        room = Output_Room.orooms[rname]
                        ##room.used = 1           # Note that someone links to this room
                        parsed_room.links[rname] = 1   # And note that we link to it
                    except KeyError:
                        parsed_room.bad_links[rname] = 1   # And note that we link to it
                        if show:
                            if verbose:
                                print >>sys.stderr, "Whoops!  Room '%(rname)s' at line %(lineno)s is not defined!" % vars()
                            Output_Room.undefs[rname] = 1
                            room = None

                if show:
                    if not optimize:
                        parsed_room.otext += "%(line_prefix)s{{Goto|{{TheStenchIndex|%(rname)s}}|%(link_text)s}}%(line_suffix)s\n" % vars()
                    elif room == None :
                        parsed_room.otext += "%(line_prefix)s{{Goto|-1|%(link_text)s}}%(line_suffix)s\n" % vars()
                    elif room.segment == parsed_room.segment :
                        parsed_room.otext += "%s{{Goto|%s|%s}}%s\n" % (line_prefix, Output_Room.orooms[rname].ordinal, link_text, line_suffix)
                    else:       # Uh oh -- we need an external link here
                        pn = G.simple_macros[".PART_PREFIX"] # Don't catch this.  If it throws something needs fixing pretty badly.
                        ps = G.segment_tags[room.segment]
                        link = "%s{{Goto|page=%s%s|%s|%s}}%s\n" % (line_prefix, pn, ps, room.ordinal, link_text, line_suffix)
                        parsed_room.otext += link
            elif show :
		parsed_room.otext += line + "\n"

# ****************************************************************
#
# Args:
#
#  \@room_line_array
#  \@subst_arrays
#  room_start_line
#
# Pass in an array of lines, which is the room spec, and an array of substititions
# to be made, where each def is:a pair "string", "string".
# It also takes a starting line number, so it can figure out what line number to complain
# about when there's an error.

prm_a_equals_b = re.compile (r""" \s*(.+?)\s* = \s*(.+?)\s*
					    (?:;(.*))?
                                   $
                                   """, re.X)

prm_bracketed_subex = re.compile (r"(.*)  (\[ [^\[\]]* \])  (.*) $", re.X)

def parse_room(iroom, show) :
    if debug :
	n = 0
	print >>sys.stderr, "Room (with %s lines):" % (len(iroom.itext))
	for l in iroom.itext:
	    n += 1
	    print >>sys.stderr, "Line %(n)s: '%(l)s'" % vars()
	print >>sys.stderr, "End room."
    
    from_strings = []
    to_string_list = []
    line = iroom.itext[0]
    if line == ".KEEP":
        line = iroom.itext[1]
    if debug:
	rl = len(iroom.itext)
	print >>sys.stderr, "Room lines: %(rl)s; Line: %(line)s" % vars()
    
    ##if r"=" in line:          # Sub strings of form "A = B, ..."
    if re.search (r"=", line):          # Sub strings of form "A = B, ..."
        how_many = None
	while line and save_re_match (prm_a_equals_b, line) :
            sub_for = G.m.group(1)
            sub_to = G.m.group(2)
            tail = G.m.group(3)
	    #
	    # We'll now attempt to parse all bracketed subexpressions before we split the list
	    #
	    while save_re_match (prm_bracketed_subex, sub_to):
                front = G.m.group(1)
                mid = G.m.group(2)
                back = G.m.group(3)
		mid = expand_state_expression (iroom.lineno, mid)
		sub_to = "%(front)s%(mid)s%(back)s" % vars()
	    
	    sub_to_list = []
	    sub_exprs = sub_to.split (",")       # This is a hack.  If there are commas inside brackets we'll mess up.
	    for se in sub_exprs:                 # Expand each term in the comma separated list
		se = expand_state_expression (iroom.lineno, se)
		for piece in se.split (",") :    # And then split it and push the pieces
		    sub_to_list.append (piece)
		
	    if how_many == None:
		how_many = len(sub_to_list);     # Note this.  We use the number on the first list.
	    
	    from_strings.append (sub_for)	 # Array of from strings
	    to_string_list.append (sub_to_list)  # Array of target lists

	    line = tail;                        # discard the head, which we've already used
	
	for t in xrange(how_many) :             # For each target, set up the translations for one instantiation
            string_pairs = []
	    for i in xrange (len(from_strings)) : # Set up the global "to" list
                fs = from_strings[i]
		tl = to_string_list[i]
                if t < len(tl):
                    ts = tl[t];                 # Pick the current translation off the list for this 'from' variable
                else:
		    ts = tl[0];                 # Use the first one if we've run off the end.
                string_pairs.append ((fs, ts))
	    if debug :
		print >>sys.stderr, "Substituting for one instantiation:"
		for s in string_pairs :
		    print >>sys.stderr, "  %s --> %s" % (s[0], s[1])
            parse_room_worker (iroom.itext, iroom.lineno, show, string_pairs)
    else :
	if debug:
            print >>sys.stderr, "Calling parse_room_worker with no translation"; 
        parse_room_worker (iroom.itext, iroom.lineno, show, [])

# ****************************************************************
# Build a room definition

build_room_mtime = 0.0

def build_room(room_text, room_start_line) :
    global build_room_mtime
    if debug :
	n = 0
	print >>sys.stderr, "Room (with %s lines):" % (len(room_text))
	for l in room_text:
	    n += 1
	    print >>sys.stderr, "Line %(n)s: '%(l)s'" % vars()
	print >>sys.stderr, "End room."
    
    st = time.time()
    room_text = expand_macros (room_start_line, room_text); # Expand everything in it so we can parse it after expansion
    dt = time.time() - st
    build_room_mtime += dt

    if debug :
	n = 0
	print >>sys.stderr, "Room after macro expansion (with %s lines):" % (len(room_text))
	for l in room_text :
	    n += 1
	    print >>sys.stderr, "Line %(n)s: '%(l)s'" % vars()
	print >>sys.stderr, "End room."

    ln = 0
    ##force_keep = False
    line = room_text[ln]
    ln += 1
    if line == ".KEEP":
        ##force_keep = True
        line = room_text[ln]
        ln += 1
    
    if re.search (r"=", line):          # Sub strings of form "A = B, ..."
        line = room_text[ln]
        ln += 1
    input_name = "%s-%s" % (line, room_start_line)
    ##if force_keep:
    ##    work_name = re.sub (r"(.*?)[A-Z0-9]*$", r"\1",line) # Trim trailing uppercase letters and numbers from the declared name
    ##    Input_Room.required_rooms.append (work_name)

    rm = Input_Room (input_name, room_start_line)
    rm.itext = room_text

# ****************************************************************

def parse_file () :
    ##global pf_roomno
    print >>sys.stderr, "parse_file(), at time %.2f" % (time.time() - start_time)

    ##pf_roomno = -1
    Room.reset_roomno()

    room_line_no = 0
    lineno = 0
    room = []
    in_room = 0
    in_comment = 0

    infile = open (G.filename, "r")
    prev_line = ""
    for line in infile:
        line = re.sub (r"\s*\n$", "", line)                # Chomp
        if prev_line:
            line = prev_line + line
            prev_line = ""
        lineno += 1

        # Merge lines which end in hidden newline
        if save_match (r"(.*?)\s*\\", line):                 # Trailing backslash?
            prev_line = G.m.group(1);
            continue

        # Strip comments
        if not keep_comments:
            if in_comment:              # Are we stripping a multiline comment?
                if debug:
                    dprint ("Stripping multiline comment: Line %s, line '%s'" % (lineno, line))
                if re.search (r"-->", line):	# Comment terminator?
                    in_comment = 0;	      # Done with that comment
                    line = re.sub (r"^.*?-->\s*", "", line)   # Strip the terminator from the front of the line
                    if re.match (r"\s*$", line):              # Nothing but blanks left?
                        continue		    # Discard the line
                else: # No terminator => just discard the line and keep going
                    continue

            if re.search (r"<!--.*-->", line):	# Remove balanced comments and surrounding blanks
                if debug:
                    dprint ("End multiline comment: Line %s, line '%s'" % (lineno, line))
                line = re.sub (r"\s*<!--.*?-->\s*", "", line)
                if re.match (r"\s*$", line):	# If there was nothing but a comment skip the line
                    continue

            if re.search (r"<!--", line): # Remove comment start (and preceding blanks) and move to comment state
                if debug:
                    dprint ("Start multiline comment: Line %s, line '%s'" % (lineno, line))
                line = re.sub (r"\s*<!--.*", "", line)
                in_comment = 1
                if re.match (r"\s*$", line):	# If there's nothing on the line but blanks and a comment start, discard it
                    continue

        if (preprocess_defs(lineno,line)) :
            continue

        if re.match (r"=================*\s*$", line):
            if debug:
                print >>sys.stderr, "Found a room divider"
            in_room = 1
            if len(room) > 0:           # Do we have an old one which needs to be parsed?
                if debug:
                    rlen = len(room)
                    print >>sys.stderr, "At line %(lineno)s: Looks like room was defined, with length %(rlen)s, so we're parsing it" % vars()
                build_room (room, room_line_no)
            room = []
            room_line_no = lineno; # Starting line number for the next room
        elif (in_room) :
            if debug:
                print >>sys.stderr, "Pushing line '%(line)s' into room"  % vars()
            room.append (line)

    infile.close()

    # If we had a last room which wasn't appended, append it now
    if len(room) > 0:
        if debug:
            print >>sys.stderr, "At line %(lineno)s: Looks like room was defined so we're parsing it" % vars()
        build_room (room, room_line_no)
        room = []

    #
    # And now, add a "room" for each state, containing the inventory for that state
    #
    if generate_inventory:
        print >>sys.stderr, "Generating inventory lists for %s states at time %.2f ... " % (len(State.by_number), time.time() - start_time)
        irooms = []
        text_lines = 0
        for s in State.by_number :
            room = []
            snumber = s.number
            try:
                G.shared_ilist_for_state[int(snumber)]      # Skip states that share someone else's inventory list
                continue
            except KeyError:                                # Key error => nothing at that entry in the table
                pass

            room.append ("01-Inventory-%(snumber)s" % vars())
            room.append ("<!-- Inventory for state %(snumber)s -->" % vars())
            try:
                for line in s.ilist:
                    room.append (G.tag_to_inventory_item[line])
            except KeyError:
                print >>sys.stderr, "State %s inventory:" % s.name
                for i in s.ilist:
                    print >>sys.stderr, "   %s" % i
                print >>sys.stderr, "State %s attributes:" % s.name
                for i in s.attributes:
                    print >>sys.stderr, "   %s" % i
                Whoops ("Parse_file, emitting inventory lists: State '%s' (number %s), with %s inventory items:  '%s' not valid" % 
                        (s.name, s.number, len(s.ilist), line))
                
            room.append ("");
            room.append ("");
            room.append ("[[../01-Inventory-extra-%s|''Additional Super-Secret Game State'']]" % (snumber));
            room.append (".NOINVENTORY")
            irooms.append (room)
            text_lines += len(room)

            #
            # And now build the associated 'extra state' room
            #
            room = []
            room.append ("01-Inventory-extra-%(snumber)s" % vars())
            room.append ("<!-- Extra attributes for state %(snumber)s -->" % vars())
            room.append ("''Additional attributes for your current state (you understand you're not really supposed to look at this, right?  It's kind of cheating a little bit):''")
            try:
                for line in s.attributes:
                    attr = G.tag_to_attribute[line]
                    if attr == "-":
                        attr = line
                    room.append ("* %s<br>" % attr)
            except KeyError:
                print >>sys.stderr, "State %s inventory:" % s.name
                for i in s.ilist:
                    print >>sys.stderr, "   %s" % i
                print >>sys.stderr, "State %s attributes:" % s.name
                for i in s.attributes:
                    print >>sys.stderr, "   %s" % i
                Whoops ("Parse_file, emitting inventory lists: State '%s' (number %s), with %s attribute items:  '%s' not valid" % 
                        (s.name, s.number, len(s.attributes), line))
                
            room.append ("");
            room.append (".NOINVENTORY")
            irooms.append (room)
            text_lines += len(room)

            pass
        if debug:
            dprint ("Done making text for inventory rooms at time %.2f" % (time.time() - start_time))
            dprint ("  Inventory rooms: %s; Text lines in them: %s; Average length: %.2f" % \
                                (len(irooms), text_lines, float(text_lines) / float(len(irooms))))
        for r in irooms:
            build_room (r, lineno)
            pass
        print >>sys.stderr, "Done with the inventory lists at time %.2f.  build_room_mtime = %.2f" % (time.time() - start_time, build_room_mtime)
        pass
    print >>sys.stderr, "Parse_file done at time %.2f" % (time.time() - start_time)
    pass

# ****************************************************************
# Process the input rooms

def digest_rooms (show):
    print >>sys.stderr, "digest_rooms(show=%s) at time %.2f" % (show, time.time() - start_time)
    Room.reset_roomno()

    ##if len (Input_Room.required_rooms) == 0:
    ##    Whoops ("There are no required rooms!")

    ##for r in Input_Room.required_rooms:
    ##    dprint ("digest_rooms:  Putting required room %s on the work queue" % r)
    ##    Input_Room.work_queue.append (r)

    ##while len(Input_Room.work_queue) > 0:
    ##    short_name = Input_Room.work_queue.pop()
    ##    try:
    ##        room_list = Input_Room.built_room_lists[short_name]
    ##    except KeyError:
    ##        continue
    ##    for r in room_list:
    ##        if r.digested == show: # Has this already been processed?
    ##            continue
    ##        elif show - r.digested > 1: # Whoops!
    ##            Whoops ("Room %s:  digested = %s, show = %s -- seem to have skipped a phase!" % (r.name, r.digested, show))
    ##        r.digested = show
    ##        parse_room (r, show)

    for r in Input_Room.irooms:
        try:
            parse_room (r, show)
        except Mice:
            print >>sys.stderr, "Caught a mouse in room number %s, named %s, near line %s -- soldiering on" % (r.number, r.name, r.lineno)
            pass
    print >>sys.stderr, "Digest_rooms done at time %.2f" % (time.time() - start_time)
    pass

# ****************************************************************
# Emit the output

def emit_rooms (segnum = None):
    try:
        ttext = G.simple_macros[".TITLE"]
        print >>G.of, "{{title|%(ttext)s}}" % vars()
    except KeyError:
        pass

    print >>G.of, "{{#switch:{{Get}}"

    if segnum:                  # If we're not in segment zero
        try:
            front_room = G.simple_macros[".SPLITFILE_ROOM_0"]
            print >>G.of, "|0="
            lines = front_room.split ("\n")
            lines = expand_macros (0, lines); # Expand everything in it in case it's built with macros
            for line in lines:
                print >>G.of, line
        except KeyError:
            pass
        pass

    output_room_count = 0
    for rname in sorted(Output_Room.orooms.keys()) :
        room = Output_Room.orooms[rname]
        if segnum != None and room.segment != segnum: # Skip anything for other segments
            continue
        if not room.used:
            if debug:
                print >>sys.stderr, "SKIPPING unused room %s, number %s, defined at line %s" % (rname, room.number, room.lineno)
            continue
        if debug:
            print >>sys.stderr, "Printing room %s, number %s, defined at line %s" % (rname, room.number, room.lineno)
        output_room_count += 1
        if not  optimize:
            print >>G.of, "<!--================================================================-->"
        print >>G.of, room.otext,

    output_room_count += 1
    print >>G.of, "<!--================================================================-->"
    print >>G.of, "<!-- Dummy room to soak up gotos that get lost in space -->"
    print >>G.of, "|"
    try:
        rv = G.simple_macros[".DUMMY"]
        print >>G.of, rv
    except KeyError:
        print >>G.of, "''You have fallen through a hole in the air.''";
        print >>G.of, "";
        print >>G.of, "''This room does not exist.''";
        print >>G.of, "";
        print >>G.of, "''There is just one exit from the room.''";
        print >>G.of, "";
        print >>G.of, "* [[Special:Random|Leave the room]]";
        print >>G.of, "* {{Goto|0|Start over}}";
        print >>G.of, "";
        print >>G.of, "{{TheStench}}";

    print >>G.of, "}}"

    print >>sys.stderr, "Read %s rooms, emitted %s rooms" % (len(Input_Room.irooms), output_room_count)
    
# ****************************************************************

parse_file ()		# Read the file

if dump_states:
    State.dump_states()

if do_digest_rooms:
    digest_rooms(0)         # Digest the rooms, build the index
    used_count = Output_Room.gc()        # And GC after phase 1
    print >>sys.stderr, "GC reported %s used rooms" % used_count

# Build the sorted index, which we use for optimized output as well as for dumping the index
room_no = 0
cur_seg = 0                     # Assign segment numbers at the same time
initial_allocation = 12         # We put the first 12 rooms in the first segment, and split the rest
for rkey in sorted (Output_Room.orooms.keys()) :
    r = Output_Room.orooms[rkey]
    r.ordinal = room_no         # Just stick it in the room
    room_no += 1
    if G.splitcount > 1 and r.used:        # Are we breaking it up into multiple files?
        ##print >>sys.stderr, "Marking room %s with segment %s" % (r.name, cur_seg)
        r.segment = cur_seg         # Mark it with its segment
        if initial_allocation:
            initial_allocation -= 1
        else:
            cur_seg += 1
            if (cur_seg >= G.splitcount):
                cur_seg = 0
            pass
        pass
    pass

if do_digest_rooms:
    # If we're building the file, do that now
    if not G.index_only:
        digest_rooms (1)            # Do this again now that we have an index

        first_key = (sorted(Output_Room.orooms.keys()))[0]
        first_room = Output_Room.orooms[first_key]
        first_room.used = 1         # Force the first room to be kept.

        ##Output_Room.gc()          # We need the GC results before we split up the file, which we do between phases.

        #
        # Now that we've run the garbage collector, we can emit the rooms that are still standing
        #
        if G.splitcount > 1:
            for s in xrange(G.splitcount):
                fn = G.out_filename + G.segment_tags[s]
                G.of = open (fn, 'w')
                emit_rooms(s)
                G.of.close()
        else:
            if G.out_filename:
                G.of = open (G.out_filename, 'w') # Let it throw if it's no good.
            emit_rooms()
            if G.of:
                G.of.close()

        had_orphans = 0
        for k in sorted (Output_Room.orooms.keys()) :
            if not Output_Room.orooms[k].used:
                had_orphans += 1
                if verbose:
                    if had_orphans == 1:
                        print >>sys.stderr, "Orphaned rooms:"
                    print >>sys.stderr, "  %(k)s" % vars()
                    pass
            pass

        if verbose and len(Output_Room.undefs) > 0:
            print >>sys.stderr, "Undefined rooms:"
            for k in sorted (Output_Room.undefs.keys()) :
                print >>sys.stderr, "  %(k)s" % vars()

        if len(Output_Room.referenced_undefs) > 0:
            print >>sys.stderr, "There were %s referenced and undefined rooms:" % len(Output_Room.referenced_undefs)
            for r in sorted(Output_Room.referenced_undefs.keys()):
                rec = Output_Room.referenced_undefs[r]
                print >>sys.stderr, "    %-32s %s times, first at line %4d by room %s" % (r, rec[2], rec[0], rec[1])

        print >>sys.stderr, "Total undefined rooms: %s; Total orphaned rooms: %s" % (len(Output_Room.undefs), had_orphans)

# If we're building the index, dump it
if G.index_only:
    print >>G.of, "<noinclude>Template to convert room name to index</noinclude>"
    print >>G.of, "{{#switch: {{{1}}}"
    for r in sorted (Output_Room.orooms.keys()) :
	print >>G.of, "| %s = %s"  % (r, Output_Room.orooms[r].ordinal)

    print >>G.of, "| -1}}\n";
    print >>G.of, r"<noinclude>[[Category:Templates]]</noinclude>"