IllogiGames:Sinkhole/cv dungeon 2
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>"