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>"