Example #1
0
class ZObjectParser(object):

  def __init__(self, zmem):

    self._memory = zmem
    self._propdefaults_addr = zmem.read_word(0x0a)
    self._stringfactory = ZStringFactory(self._memory)

    if 1 <= self._memory.version <= 3:
      self._objecttree_addr = self._propdefaults_addr + 62
    elif 4 <= self._memory.version <= 5:
      self._objecttree_addr = self._propdefaults_addr + 126
    else:
      raise ZObjectIllegalVersion


  def _get_object_addr(self, objectnum):
    """Return address of object number OBJECTNUM."""

    result = 0
    if 1 <= self._memory.version <= 3:
      if not (1 <= objectnum <= 255):
        raise ZObjectIllegalObjectNumber
      result = self._objecttree_addr + (9 * (objectnum - 1))
    elif 4 <= self._memory.version <= 5:
      if not (1 <= objectnum <= 65535):
        log("error:  there is no object %d" % objectnum)
        raise ZObjectIllegalObjectNumber
      result = self._objecttree_addr + (14 * (objectnum - 1))
    else:
      raise ZObjectIllegalVersion

    log("address of object %d is %d" % (objectnum, result))
    return result


  def _get_parent_sibling_child(self, objectnum):
    """Return [parent, sibling, child] object numbers of object OBJECTNUM."""

    addr = self._get_object_addr(objectnum)

    result = 0
    if 1 <= self._memory.version <= 3:
      addr += 4  # skip past attributes
      result = self._memory[addr:addr+3]

    elif 4 <= self._memory.version <= 5:
      addr += 6  # skip past attributes
      result = [self._memory.read_word(addr),
                self._memory.read_word(addr + 2),
                self._memory.read_word(addr + 4)]
    else:
      raise ZObjectIllegalVersion

    log ("parent/sibling/child of object %d is %d, %d, %d" %
         (objectnum, result[0], result[1], result[2]))
    return result
    

  def _get_proptable_addr(self, objectnum):
    """Return address of property table of object OBJECTNUM."""

    addr = self._get_object_addr(objectnum)

    # skip past attributes and relatives
    if 1 <= self._memory.version <= 3:
      addr += 7
    elif 4 <= self._memory.version <= 5:
      addr += 12
    else:
      raise ZObjectIllegalVersion

    return self._memory.read_word(addr)

  def _get_default_property_addr(self, propnum):
    """Return address of default value for property PROPNUM."""

    addr = self._propdefaults_addr

    if 1 <= self._memory.version <= 3:
      if not (1 <= propnum <= 31):
        raise ZObjectIllegalPropertyNumber
    elif 4 <= self._memory.version <= 5:
      if not (1 <= propnum <= 63):
        raise ZObjectIllegalPropertyNumber
    else:
      raise ZObjectIllegalVersion

    return (addr + (2 * (propnum - 1)))


  #--------- Public APIs -----------

  def get_attribute(self, objectnum, attrnum):
    """Return value (0 or 1) of attribute number ATTRNUM of object
    number OBJECTNUM."""

    object_addr = self._get_object_addr(objectnum)

    if 1 <= self._memory.version <= 3:
      if not (0 <= attrnum <= 31):
        raise ZObjectIllegalAttributeNumber
      bf = BitField(self._memory[object_addr + (attrnum / 8)])

    elif 4 <= self._memory.version <= 5:
      if not (0 <= attrnum <= 47):
        raise ZObjectIllegalAttributeNumber
      bf = BitField(self._memory[object_addr + (attrnum / 8)])

    else:
      raise ZObjectIllegalVersion

    return bf[7 - (attrnum % 8)]


  def get_all_attributes(self, objectnum):
    """Return a list of all attribute numbers that are set on object
    OBJECTNUM"""

    if 1 <= self._memory.version <= 3:
      max = 32
    elif 4 <= self._memory.version <= 5:
      max = 48
    else:
      raise ZObjectIllegalVersion

    # really inefficient, but who cares?
    attrs = []
    for i in range (0, max):
      if self.get_attribute(objectnum, i):
        attrs.append(i)
    return attrs


  def get_parent(self, objectnum):
    """Return object number of parent of object number OBJECTNUM."""

    [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
    return parent


  def get_child(self, objectnum):
    """Return object number of child of object number OBJECTNUM."""

    [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
    return child


  def get_sibling(self, objectnum):
    """Return object number of sibling of object number OBJECTNUM."""

    [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
    return sibling


  def set_parent(self, objectnum, new_parent_num):
    """Make OBJECTNUM's parent pointer point to NEW_PARENT_NUM."""

    addr = self._get_object_addr(objectnum)
    if 1 <= self._memory.version <= 3:
      self._memory[addr + 4] = new_parent_num
    elif 4 <= self._memory.version <= 5:
      self._memory.write_word(addr + 6, new_parent_num)
    else:
      raise ZObjectIllegalVersion


  def set_child(self, objectnum, new_child_num):
    """Make OBJECTNUM's child pointer point to NEW_PARENT_NUM."""

    addr = self._get_object_addr(objectnum)
    if 1 <= self._memory.version <= 3:
      self._memory[addr + 6] = new_child_num
    elif 4 <= self._memory.version <= 5:
      self._memory.write_word(addr + 10, new_child_num)
    else:
      raise ZObjectIllegalVersion


  def set_sibling(self, objectnum, new_sibling_num):
    """Make OBJECTNUM's sibling pointer point to NEW_PARENT_NUM."""

    addr = self._get_object_addr(objectnum)
    if 1 <= self._memory.version <= 3:
      self._memory[addr + 5] = new_sibling_num
    elif 4 <= self._memory.version <= 5:
      self._memory.write_word(addr + 8, new_sibling_num)
    else:
      raise ZObjectIllegalVersion


  def insert_object(self, parent_object, new_child):
    """Prepend object NEW_CHILD to the list of PARENT_OBJECT's children."""

    # Remember all the original pointers within the new_child
    [p, s, c] = self._get_parent_sibling_child(new_child)

    # First insert new_child intto the parent_object
    original_child = self.get_child(parent_object)
    self.set_sibling(new_child, original_child)
    self.set_parent(new_child, parent_object)
    self.set_child(parent_object, new_child)

    if p == 0:  # no need to 'remove' new_child, since it wasn't in a tree
      return

    # Hunt down and remove the new_child from its old location
    item = self.get_child(p)
    if item == 0:
      # new_object claimed to have parent p, but p has no children!?
      raise ZObjectMalformedTree
    elif item == new_child:  # done!  new_object was head of list
      self.set_child(p, s) # note that s might be 0, that's fine.
    else: # walk across list of sibling links
      prev = item
      current = self.get_sibling(item)
      while current != 0:
        if current == new_child:
          self.set_sibling(prev, s) # s might be 0, that's fine.
          break
      else:
        # we reached the end of the list, never got a match
        raise ZObjectMalformedTree


  def get_shortname(self, objectnum):
    """Return 'short name' of object number OBJECTNUM as ascii string."""

    addr = self._get_proptable_addr(objectnum)
    return self._stringfactory.get(addr+1)


  def get_prop(self, objectnum, propnum):
    """Return either a byte or word value of property PROPNUM of
    object OBJECTNUM."""
    (addr, size) = self.get_prop_addr_len(objectnum, propnum)
    if size == 1:
      return self._memory[addr]
    elif size == 2:
      return self._memory.read_word(addr)
    else:
      raise ZObjectIllegalPropLength


  def get_prop_addr_len(self, objectnum, propnum):
    """Return address & length of value for property number PROPNUM of
    object number OBJECTNUM.  If object has no such property, then
    return the address & length of the 'default' value for the property."""

    # start at the beginning of the object's proptable
    addr = self._get_proptable_addr(objectnum)
    # skip past the shortname of the object
    addr += (2 * self._memory[addr])
    pnum = 0

    if 1 <= self._memory.version <= 3:

      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[4:0]
        size = bf[7:5] + 1
        if pnum == propnum:
          return (addr, size)
        addr += size

    elif 4 <= self._memory.version <= 5:

      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[5:0]
        if bf[7]:
          bf2 = BitField(self._memory[addr])
          addr += 1
          size = bf2[5:0]
        else:
          if bf[6]:
            size = 2
          else:
            size = 1
        if pnum == propnum:
          return (addr, size)
        addr += size

    else:
      raise ZObjectIllegalVersion

    # property list ran out, so return default propval instead.
    default_value_addr = self._get_default_property_addr(propnum)
    return (default_value_addr, 2)


  def get_all_properties(self, objectnum):
    """Return a dictionary of all properties listed in the property
    table of object OBJECTNUM.  (Obviously, this discounts 'default'
    property values.).  The dictionary maps property numbers to (addr,
    len) propval tuples."""

    proplist = {}

    # start at the beginning of the object's proptable
    addr = self._get_proptable_addr(objectnum)
    # skip past the shortname of the object
    shortname_length = self._memory[addr]
    addr += 1
    addr += (2*shortname_length)

    if 1 <= self._memory.version <= 3:
      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[4:0]
        size = bf[7:5] + 1
        proplist[pnum] = (addr, size)
        addr += size

    elif 4 <= self._memory.version <= 5:
      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[0:6]
        if bf[7]:
          bf2 = BitField(self._memory[addr])
          addr += 1
          size = bf2[0:6]
          if size == 0:
            size = 64
        else:
          if bf[6]:
            size = 2
          else:
            size = 1
        proplist[pnum] = (addr, size)
        addr += size

    else:
      raise ZObjectIllegalVersion

    return proplist


  def set_property(self, objectnum, propnum, value):
    """Set a property on an object."""
    proplist = self.get_all_properties(objectnum)
    if propnum not in proplist:
      raise ZObjectIllegalPropertyNumber

    addr, size = proplist[propnum]
    if size == 1:
      self._memory[addr] = (value & 0xFF)
    elif size == 2:
      self._memory.write_word(addr, value)
    else:
      raise ZObjectIllegalPropertySet


  def describe_object(self, objectnum):
    """For debugging purposes, pretty-print everything known about
    object OBJECTNUM."""

    print "Object number:", objectnum
    print "    Short name:", self.get_shortname(objectnum)
    print "    Parent:", self.get_parent(objectnum),
    print " Sibling:", self.get_sibling(objectnum),
    print " Child:", self.get_child(objectnum)
    print "    Attributes:", self.get_all_attributes(objectnum)
    print "    Properties:"

    proplist = self.get_all_properties(objectnum)
    for key in proplist.keys():
      (addr, len) = proplist[key]
      print "       [%2d] :" % key,
      for i in range(0, len):
        print "%02X" % self._memory[addr+i],
      print
Example #2
0
class ZObjectParser(object):
    def __init__(self, zmem):

        self._memory = zmem
        self._propdefaults_addr = zmem.read_word(0x0a)
        self._stringfactory = ZStringFactory(self._memory)

        if 1 <= self._memory.version <= 3:
            self._objecttree_addr = self._propdefaults_addr + 62
        elif 4 <= self._memory.version <= 5:
            self._objecttree_addr = self._propdefaults_addr + 126
        else:
            raise ZObjectIllegalVersion

    def _get_object_addr(self, objectnum):
        """Return address of object number OBJECTNUM."""

        result = 0
        if 1 <= self._memory.version <= 3:
            if not (1 <= objectnum <= 255):
                raise ZObjectIllegalObjectNumber
            result = self._objecttree_addr + (9 * (objectnum - 1))
        elif 4 <= self._memory.version <= 5:
            if not (1 <= objectnum <= 65535):
                log("error:  there is no object %d" % objectnum)
                raise ZObjectIllegalObjectNumber
            result = self._objecttree_addr + (14 * (objectnum - 1))
        else:
            raise ZObjectIllegalVersion

        log("address of object %d is %d" % (objectnum, result))
        return result

    def _get_parent_sibling_child(self, objectnum):
        """Return [parent, sibling, child] object numbers of object OBJECTNUM."""

        addr = self._get_object_addr(objectnum)

        result = 0
        if 1 <= self._memory.version <= 3:
            addr += 4  # skip past attributes
            result = self._memory[addr:addr + 3]

        elif 4 <= self._memory.version <= 5:
            addr += 6  # skip past attributes
            result = [
                self._memory.read_word(addr),
                self._memory.read_word(addr + 2),
                self._memory.read_word(addr + 4)
            ]
        else:
            raise ZObjectIllegalVersion

        log("parent/sibling/child of object %d is %d, %d, %d" %
            (objectnum, result[0], result[1], result[2]))
        return result

    def _get_proptable_addr(self, objectnum):
        """Return address of property table of object OBJECTNUM."""

        addr = self._get_object_addr(objectnum)

        # skip past attributes and relatives
        if 1 <= self._memory.version <= 3:
            addr += 7
        elif 4 <= self._memory.version <= 5:
            addr += 12
        else:
            raise ZObjectIllegalVersion

        return self._memory.read_word(addr)

    def _get_default_property_addr(self, propnum):
        """Return address of default value for property PROPNUM."""

        addr = self._propdefaults_addr

        if 1 <= self._memory.version <= 3:
            if not (1 <= propnum <= 31):
                raise ZObjectIllegalPropertyNumber
        elif 4 <= self._memory.version <= 5:
            if not (1 <= propnum <= 63):
                raise ZObjectIllegalPropertyNumber
        else:
            raise ZObjectIllegalVersion

        return (addr + (2 * (propnum - 1)))

    #--------- Public APIs -----------

    def get_attribute(self, objectnum, attrnum):
        """Return value (0 or 1) of attribute number ATTRNUM of object
    number OBJECTNUM."""

        object_addr = self._get_object_addr(objectnum)

        if 1 <= self._memory.version <= 3:
            if not (0 <= attrnum <= 31):
                raise ZObjectIllegalAttributeNumber
            bf = BitField(self._memory[object_addr + (attrnum / 8)])

        elif 4 <= self._memory.version <= 5:
            if not (0 <= attrnum <= 47):
                raise ZObjectIllegalAttributeNumber
            bf = BitField(self._memory[object_addr + (attrnum / 8)])

        else:
            raise ZObjectIllegalVersion

        return bf[7 - (attrnum % 8)]

    def get_all_attributes(self, objectnum):
        """Return a list of all attribute numbers that are set on object
    OBJECTNUM"""

        if 1 <= self._memory.version <= 3:
            max = 32
        elif 4 <= self._memory.version <= 5:
            max = 48
        else:
            raise ZObjectIllegalVersion

        # really inefficient, but who cares?
        attrs = []
        for i in range(0, max):
            if self.get_attribute(objectnum, i):
                attrs.append(i)
        return attrs

    def get_parent(self, objectnum):
        """Return object number of parent of object number OBJECTNUM."""

        [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
        return parent

    def get_child(self, objectnum):
        """Return object number of child of object number OBJECTNUM."""

        [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
        return child

    def get_sibling(self, objectnum):
        """Return object number of sibling of object number OBJECTNUM."""

        [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
        return sibling

    def set_parent(self, objectnum, new_parent_num):
        """Make OBJECTNUM's parent pointer point to NEW_PARENT_NUM."""

        addr = self._get_object_addr(objectnum)
        if 1 <= self._memory.version <= 3:
            self._memory[addr + 4] = new_parent_num
        elif 4 <= self._memory.version <= 5:
            self._memory.write_word(addr + 6, new_parent_num)
        else:
            raise ZObjectIllegalVersion

    def set_child(self, objectnum, new_child_num):
        """Make OBJECTNUM's child pointer point to NEW_PARENT_NUM."""

        addr = self._get_object_addr(objectnum)
        if 1 <= self._memory.version <= 3:
            self._memory[addr + 6] = new_child_num
        elif 4 <= self._memory.version <= 5:
            self._memory.write_word(addr + 10, new_child_num)
        else:
            raise ZObjectIllegalVersion

    def set_sibling(self, objectnum, new_sibling_num):
        """Make OBJECTNUM's sibling pointer point to NEW_PARENT_NUM."""

        addr = self._get_object_addr(objectnum)
        if 1 <= self._memory.version <= 3:
            self._memory[addr + 5] = new_sibling_num
        elif 4 <= self._memory.version <= 5:
            self._memory.write_word(addr + 8, new_sibling_num)
        else:
            raise ZObjectIllegalVersion

    def insert_object(self, parent_object, new_child):
        """Prepend object NEW_CHILD to the list of PARENT_OBJECT's children."""

        # Remember all the original pointers within the new_child
        [p, s, c] = self._get_parent_sibling_child(new_child)

        # First insert new_child intto the parent_object
        original_child = self.get_child(parent_object)
        self.set_sibling(new_child, original_child)
        self.set_parent(new_child, parent_object)
        self.set_child(parent_object, new_child)

        if p == 0:  # no need to 'remove' new_child, since it wasn't in a tree
            return

        # Hunt down and remove the new_child from its old location
        item = self.get_child(p)
        if item == 0:
            # new_object claimed to have parent p, but p has no children!?
            raise ZObjectMalformedTree
        elif item == new_child:  # done!  new_object was head of list
            self.set_child(p, s)  # note that s might be 0, that's fine.
        else:  # walk across list of sibling links
            prev = item
            current = self.get_sibling(item)
            while current != 0:
                if current == new_child:
                    self.set_sibling(prev, s)  # s might be 0, that's fine.
                    break
            else:
                # we reached the end of the list, never got a match
                raise ZObjectMalformedTree

    def get_shortname(self, objectnum):
        """Return 'short name' of object number OBJECTNUM as ascii string."""

        addr = self._get_proptable_addr(objectnum)
        return self._stringfactory.get(addr + 1)

    def get_prop(self, objectnum, propnum):
        """Return either a byte or word value of property PROPNUM of
    object OBJECTNUM."""
        (addr, size) = self.get_prop_addr_len(objectnum, propnum)
        if size == 1:
            return self._memory[addr]
        elif size == 2:
            return self._memory.read_word(addr)
        else:
            raise ZObjectIllegalPropLength

    def get_prop_addr_len(self, objectnum, propnum):
        """Return address & length of value for property number PROPNUM of
    object number OBJECTNUM.  If object has no such property, then
    return the address & length of the 'default' value for the property."""

        # start at the beginning of the object's proptable
        addr = self._get_proptable_addr(objectnum)
        # skip past the shortname of the object
        addr += (2 * self._memory[addr])
        pnum = 0

        if 1 <= self._memory.version <= 3:

            while self._memory[addr] != 0:
                bf = BitField(self._memory[addr])
                addr += 1
                pnum = bf[4:0]
                size = bf[7:5] + 1
                if pnum == propnum:
                    return (addr, size)
                addr += size

        elif 4 <= self._memory.version <= 5:

            while self._memory[addr] != 0:
                bf = BitField(self._memory[addr])
                addr += 1
                pnum = bf[5:0]
                if bf[7]:
                    bf2 = BitField(self._memory[addr])
                    addr += 1
                    size = bf2[5:0]
                else:
                    if bf[6]:
                        size = 2
                    else:
                        size = 1
                if pnum == propnum:
                    return (addr, size)
                addr += size

        else:
            raise ZObjectIllegalVersion

        # property list ran out, so return default propval instead.
        default_value_addr = self._get_default_property_addr(propnum)
        return (default_value_addr, 2)

    def get_all_properties(self, objectnum):
        """Return a dictionary of all properties listed in the property
    table of object OBJECTNUM.  (Obviously, this discounts 'default'
    property values.).  The dictionary maps property numbers to (addr,
    len) propval tuples."""

        proplist = {}

        # start at the beginning of the object's proptable
        addr = self._get_proptable_addr(objectnum)
        # skip past the shortname of the object
        shortname_length = self._memory[addr]
        addr += 1
        addr += (2 * shortname_length)

        if 1 <= self._memory.version <= 3:
            while self._memory[addr] != 0:
                bf = BitField(self._memory[addr])
                addr += 1
                pnum = bf[4:0]
                size = bf[7:5] + 1
                proplist[pnum] = (addr, size)
                addr += size

        elif 4 <= self._memory.version <= 5:
            while self._memory[addr] != 0:
                bf = BitField(self._memory[addr])
                addr += 1
                pnum = bf[0:6]
                if bf[7]:
                    bf2 = BitField(self._memory[addr])
                    addr += 1
                    size = bf2[0:6]
                    if size == 0:
                        size = 64
                else:
                    if bf[6]:
                        size = 2
                    else:
                        size = 1
                proplist[pnum] = (addr, size)
                addr += size

        else:
            raise ZObjectIllegalVersion

        return proplist

    def set_property(self, objectnum, propnum, value):
        """Set a property on an object."""
        proplist = self.get_all_properties(objectnum)
        if propnum not in proplist:
            raise ZObjectIllegalPropertyNumber

        addr, size = proplist[propnum]
        if size == 1:
            self._memory[addr] = (value & 0xFF)
        elif size == 2:
            self._memory.write_word(addr, value)
        else:
            raise ZObjectIllegalPropertySet

    def describe_object(self, objectnum):
        """For debugging purposes, pretty-print everything known about
    object OBJECTNUM."""

        print "Object number:", objectnum
        print "    Short name:", self.get_shortname(objectnum)
        print "    Parent:", self.get_parent(objectnum),
        print " Sibling:", self.get_sibling(objectnum),
        print " Child:", self.get_child(objectnum)
        print "    Attributes:", self.get_all_attributes(objectnum)
        print "    Properties:"

        proplist = self.get_all_properties(objectnum)
        for key in proplist.keys():
            (addr, len) = proplist[key]
            print "       [%2d] :" % key,
            for i in range(0, len):
                print "%02X" % self._memory[addr + i],
            print
Example #3
0
class ZLexer(object):

  def __init__(self, mem):

    self._memory = mem
    self._stringfactory = ZStringFactory(self._memory)
    self._zsciitranslator = ZsciiTranslator(self._memory)

    # Load and parse game's 'standard' dictionary from static memory.
    dict_addr = self._memory.read_word(0x08)
    self._num_entries, self._entry_length, self._separators, entries_addr = \
                       self._parse_dict_header(dict_addr)
    self._dict = self.get_dictionary(dict_addr)


  def _parse_dict_header(self, address):
    """Parse the header of the dictionary at ADDRESS.  Return the
    number of entries, the length of each entry, a list of zscii
    word separators, and an address of the beginning the entries."""

    addr = address
    num_separators = self._memory[addr]
    separators = self._memory[(addr + 1):(addr + num_separators)]
    addr += (1 + num_separators)
    entry_length = self._memory[addr]
    addr += 1
    num_entries = self._memory.read_word(addr)
    addr += 2

    return num_entries, entry_length, separators, addr


  def _tokenise_string(self, string, separators):
     """Split unicode STRING into a list of words, and return the list.
    Whitespace always counts as a word separator, but so do any
    unicode characters provided in the list of SEPARATORS.  Note,
    however, that instances of these separators caunt as words
    themselves."""

     # re.findall(r'[,.;]|\w+', 'abc, def')
     sep_string = ""
     for sep in separators:
       sep_string += sep
     if sep_string == "":
       regex = r"\w+"
     else:
       regex = r"[%s]|\w+" % sep_string

     return re.findall(regex, string)


  #--------- Public APIs -----------


  def get_dictionary(self, address):
    """Load a z-machine-format dictionary at ADDRESS -- which maps
    zstrings to bytestrings -- into a python dictionary which maps
    unicode strings to the address of the word in the original
    dictionary.  Return the new dictionary."""

    dict = {}

    num_entries, entry_length, separators, addr = \
                 self._parse_dict_header(address)

    for i in range(0, num_entries):
      text_key = self._stringfactory.get(addr)
      dict[text_key] = addr
      addr += entry_length

    return dict


  def parse_input(self, string, dict_addr=None):
    """Given a unicode string, parse it into words based on a dictionary.

    if DICT_ADDR is provided, use the custom dictionary at that
    address to do the analysis, otherwise default to using the game's
    'standard' dictionary.

    The dictionary plays two roles: first, it specifies separator
    characters beyond the usual space character.  Second, we need to
    look up each word in the dictionary and return the address.

    Return a list of lists, each list being of the form

       [word, byte_address_of_word_in_dictionary (or 0 if not in dictionary)]
    """

    if dict_addr is None:
      zseparators = self._separators
      dict = self._dict
    else:
      num_entries, entry_length, zseparators, addr = \
                   self._parse_dict_header(dict_addr)
      dict = self.get_dictionary(dict_addr)

    # Our list of word separators are actually zscii codes that must
    # be converted to unicode before we can use them.
    separators = []
    for code in zseparators:
      separators.append(self._zsciitranslator.ztou(code))

    token_list = self._tokenise_string(string, separators)

    final_list = []
    for word in token_list:
      if dict.has_key(word):
        byte_addr = dict[word]
      else:
        byte_addr = 0
      final_list.append([word, byte_addr])

    return final_list
Example #4
0
class ZLexer(object):
    def __init__(self, mem):

        self._memory = mem
        self._stringfactory = ZStringFactory(self._memory)
        self._zsciitranslator = ZsciiTranslator(self._memory)

        # Load and parse game's 'standard' dictionary from static memory.
        dict_addr = self._memory.read_word(0x08)
        self._num_entries, self._entry_length, self._separators, entries_addr = \
                           self._parse_dict_header(dict_addr)
        self._dict = self.get_dictionary(dict_addr)

    def _parse_dict_header(self, address):
        """Parse the header of the dictionary at ADDRESS.  Return the
    number of entries, the length of each entry, a list of zscii
    word separators, and an address of the beginning the entries."""

        addr = address
        num_separators = self._memory[addr]
        separators = self._memory[(addr + 1):(addr + num_separators)]
        addr += (1 + num_separators)
        entry_length = self._memory[addr]
        addr += 1
        num_entries = self._memory.read_word(addr)
        addr += 2

        return num_entries, entry_length, separators, addr

    def _tokenise_string(self, string, separators):
        """Split unicode STRING into a list of words, and return the list.
    Whitespace always counts as a word separator, but so do any
    unicode characters provided in the list of SEPARATORS.  Note,
    however, that instances of these separators caunt as words
    themselves."""

        # re.findall(r'[,.;]|\w+', 'abc, def')
        sep_string = ""
        for sep in separators:
            sep_string += sep
        if sep_string == "":
            regex = r"\w+"
        else:
            regex = r"[%s]|\w+" % sep_string

        return re.findall(regex, string)

    #--------- Public APIs -----------

    def get_dictionary(self, address):
        """Load a z-machine-format dictionary at ADDRESS -- which maps
    zstrings to bytestrings -- into a python dictionary which maps
    unicode strings to the address of the word in the original
    dictionary.  Return the new dictionary."""

        dict = {}

        num_entries, entry_length, separators, addr = \
                     self._parse_dict_header(address)

        for i in range(0, num_entries):
            text_key = self._stringfactory.get(addr)
            dict[text_key] = addr
            addr += entry_length

        return dict

    def parse_input(self, string, dict_addr=None):
        """Given a unicode string, parse it into words based on a dictionary.

    if DICT_ADDR is provided, use the custom dictionary at that
    address to do the analysis, otherwise default to using the game's
    'standard' dictionary.

    The dictionary plays two roles: first, it specifies separator
    characters beyond the usual space character.  Second, we need to
    look up each word in the dictionary and return the address.

    Return a list of lists, each list being of the form

       [word, byte_address_of_word_in_dictionary (or 0 if not in dictionary)]
    """

        if dict_addr is None:
            zseparators = self._separators
            dict = self._dict
        else:
            num_entries, entry_length, zseparators, addr = \
                         self._parse_dict_header(dict_addr)
            dict = self.get_dictionary(dict_addr)

        # Our list of word separators are actually zscii codes that must
        # be converted to unicode before we can use them.
        separators = []
        for code in zseparators:
            separators.append(self._zsciitranslator.ztou(code))

        token_list = self._tokenise_string(string, separators)

        final_list = []
        for word in token_list:
            if dict.has_key(word):
                byte_addr = dict[word]
            else:
                byte_addr = 0
            final_list.append([word, byte_addr])

        return final_list
Example #5
0
class ZObjectParser(object):

  def __init__(self, zmem):

    self._memory = zmem
    self._propdefaults_addr = zmem.read_word(0x0a)
    self._stringfactory = ZStringFactory(self._memory)

    if 1 <= self._memory.version <= 3:
      self._objecttree_addr = self._propdefaults_addr + 62
    elif 4 <= self._memory.version <= 5:
      self._objecttree_addr = self._propdefaults_addr + 126
    else:
      raise ZObjectIllegalVersion


  def _get_object_addr(self, objectnum):
    """Return address of object number OBJECTNUM."""

    if 1 <= self._memory.version <= 3:
      if not (1 <= objectnum <= 255):
        raise ZObjectIllegalObjectNumber
      return self._objecttree_addr + (9 * (objectnum - 1))

    elif 4 <= self._memory.version <= 5:
      if not (1 <= objectnum <= 65535):
        raise ZObjectIllegalObjectNumber
      return self._objecttree_addr + (14 * (objectnum - 1))

    else:
      raise ZObjectIllegalVersion

  def _get_parent_sibling_child(self, objectnum):
    """Return [parent, sibling, child] object numbers of object OBJECTNUM."""

    addr = self._get_object_addr(objectnum)

    if 1 <= self._memory.version <= 3:
      addr += 4  # skip past attributes
      return self._memory[addr:addr+3]

    elif 4 <= self._memory.version <= 5:
      addr += 6  # skip past attributes
      return [self._memory.read_word(addr),
              self._memory.read_word(addr + 2),
              self._memory.read_word(addr + 4)]
    else:
      raise ZObjectIllegalVersion

  def _get_proptable_addr(self, objectnum):
    """Return address of property table of object OBJECTNUM."""

    addr = self._get_object_addr(objectnum)

    # skip past attributes and relatives
    if 1 <= self._memory.version <= 3:
      addr += 7
    elif 4 <= self._memory.version <= 5:
      addr += 12
    else:
      raise ZObjectIllegalVersion

    return self._memory.read_word(addr)

  def _get_default_property_addr(self, propnum):
    """Return address of default value for property PROPNUM."""

    addr = self._propdefaults_addr

    if 1 <= self._memory.version <= 3:
      if not (1 <= propnum <= 31):
        raise ZObjectIllegalPropertyNumber
    elif 4 <= self._memory.version <= 5:
      if not (1 <= propnum <= 63):
        raise ZObjectIllegalPropertyNumber
    else:
      raise ZObjectIllegalVersion

    return (addr + (2 * (propnum - 1)))


  #--------- Public APIs -----------

  def get_attribute(self, objectnum, attrnum):
    """Return value (0 or 1) of attribute number ATTRNUM of object
    number OBJECTNUM."""

    object_addr = self._get_object_addr(objectnum)

    if 1 <= self._memory.version <= 3:
      if not (0 <= attrnum <= 31):
        raise ZObjectIllegalAttributeNumber
      bf = BitField(self._memory[object_addr + (attrnum / 8)])

    elif 4 <= self._memory.version <= 5:
      if not (0 <= attrnum <= 47):
        raise ZObjectIllegalAttributeNumber
      bf = BitField(self._memory[object_addr + (attrnum / 8)])

    else:
      raise ZObjectIllegalVersion

    return bf[7 - (attrnum % 8)]


  def get_all_attributes(self, objectnum):
    """Return a list of all attribute numbers that are set on object
    OBJECTNUM"""

    if 1 <= self._memory.version <= 3:
      max = 32
    elif 4 <= self._memory.version <= 5:
      max = 48
    else:
      raise ZObjectIllegalVersion

    # really inefficient, but who cares?
    attrs = []
    for i in range (0, max):
      if self.get_attribute(objectnum, i):
        attrs.append(i)
    return attrs


  def get_parent(self, objectnum):
    """Return object number of parent of object number OBJECTNUM."""

    [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
    return parent


  def get_child(self, objectnum):
    """Return object number of child of object number OBJECTNUM."""

    [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
    return child


  def get_sibling(self, objectnum):
    """Return object number of sibling of object number OBJECTNUM."""

    [parent, sibling, child] = self._get_parent_sibling_child(objectnum)
    return sibling


  def get_shortname(self, objectnum):
    """Return 'short name' of object number OBJECTNUM as ascii string."""

    addr = self._get_proptable_addr(objectnum)
    return self._stringfactory.get(addr+1)


  def get_prop_addr_len(self, objectnum, propnum):
    """Return address & length of value for property number PROPNUM of
    object number OBJECTNUM.  If object has no such property, then
    return the address & length of the 'default' value for the property."""

    # start at the beginning of the object's proptable
    addr = self._get_proptable_addr(objectnum)
    # skip past the shortname of the object
    addr += (2 * self._memory[addr])
    pnum = 0

    if 1 <= self._memory.version <= 3:

      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[4:0]
        size = bf[7:5] + 1
        if pnum == propnum:
          return (addr, size)
        addr += size

    elif 4 <= self._memory.version <= 5:

      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[5:0]
        if bf[7]:
          bf2 = BitField(self._memory[addr])
          addr += 1
          size = bf2[5:0]
        else:
          if bf[6]:
            size = 2
          else:
            size = 1
        if pnum == propnum:
          return (addr, size)
        addr += size

    else:
      raise ZObjectIllegalVersion

    # property list ran out, so return default propval instead.
    default_value_addr = self._get_default_property_addr(propnum)
    return (default_value_addr, 2)


  def get_all_properties(self, objectnum):
    """Return a dictionary of all properties listed in the property
    table of object OBJECTNUM.  (Obviously, this discounts 'default'
    property values.).  The dictionary maps property numbers to (addr,
    len) propval tuples."""

    proplist = {}

    # start at the beginning of the object's proptable
    addr = self._get_proptable_addr(objectnum)
    # skip past the shortname of the object
    shortname_length = self._memory[addr]
    addr += 1
    addr += (2*shortname_length)

    if 1 <= self._memory.version <= 3:
      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[4:0]
        size = bf[7:5] + 1
        proplist[pnum] = (addr, size)
        addr += size

    elif 4 <= self._memory.version <= 5:
      while self._memory[addr] != 0:
        bf = BitField(self._memory[addr])
        addr += 1
        pnum = bf[0:6]
        if bf[7]:
          bf2 = BitField(self._memory[addr])
          addr += 1
          size = bf2[0:6]
          if size == 0:
            size = 64
        else:
          if bf[6]:
            size = 2
          else:
            size = 1
        proplist[pnum] = (addr, size)
        addr += size

    else:
      raise ZObjectIllegalVersion

    return proplist

  def set_property(self, objectnum, propnum, value):
    """Set a property on an object."""
    proplist = self.get_all_properties(objectnum)
    if propnum not in proplist:
      raise ZObjectIllegalPropertyNumber

    addr, size = proplist[propnum]
    if size == 1:
      self._memory[addr] = (value & 0xFF)
    elif size == 2:
      self._memory.write_word(addr, value)
    else:
      raise ZObjectIllegalPropertySet

  def describe_object(self, objectnum):
    """For debugging purposes, pretty-print everything known about
    object OBJECTNUM."""

    print "Object number:", objectnum
    print "    Short name:", self.get_shortname(objectnum)
    print "    Parent:", self.get_parent(objectnum),
    print " Sibling:", self.get_sibling(objectnum),
    print " Child:", self.get_child(objectnum)
    print "    Attributes:", self.get_all_attributes(objectnum)
    print "    Properties:"

    proplist = self.get_all_properties(objectnum)
    for key in proplist.keys():
      (addr, len) = proplist[key]
      print "       [%2d] :" % key,
      for i in range(0, len):
        print "%02X" % self._memory[addr+i],
      print