# Utility module for 'icache.py': interpret tag tables and indirect nodes.

# (This module is a bit chatty when confronted with the unexpected.)


import regexp
import string
import ifile


# Get the tag table of an open file, as a dictionary.
# Seeks around in the file; after reading, the position is undefined.
# Return an empty tag table if none is found.
#
def get_tags(f):
	#
	# First see if the last "node" is the end of tag table marker.
	#
	f.seek(0, 2) # Seek to EOF
	end = f.tell()
	buf = ifile.backup_node(f, end)
	if not labelmatch(buf, 0, 'end tag table\n'):
		return {} # No succes
	#
	# Next backup to the previous "node" -- the tag table itself.
	#
	###print 'Getting prebuilt tag table...'
	end = f.tell() - len(buf)
	buf = ifile.backup_node(f, end)
	label = 'tag table:\n'
	if not labelmatch(buf, 0, label):
		print 'Weird: end tag table marker but no tag table?'
		print 'Node begins:', `buf[:50]`
		return {}
	#
	# Now read the whole tag table.
	#
	end = f.tell() - len(buf) # Do this first!
	buf = ifile.read_node(f, buf)
	#
	# First check for an indirection table.
	#
	indirlist = []
	if labelmatch(buf, len(label), '(indirect)\n'):
		indirbuf = ifile.backup_node(f, end)
		if not labelmatch(indirbuf, 0, 'indirect:\n'):
			print 'Weird: promised indirection table not found'
			print 'Node begins:', `indirbuf[:50]`
			# Carry on.  Things probably won't work though.
		else:
			indirbuf = ifile.read_node(f, indirbuf)
			indirlist = parse_indirlist(indirbuf)
	#
	# Now parse the tag table.
	#
	findtag = regexp.compile('^(.*[nN]ode:[ \t]*(.*))\177([0-9]+)$').match
	i = 0
	tags = {}
	while 1:
		match = findtag(buf, i)
		if not match:
			break
		(a,b), (a1,b1), (a2,b2), (a3,b3) = match
		i = b
		line = buf[a1:b1]
		node = string.lower(buf[a2:b2])
		offset = eval(buf[a3:b3]) # XXX What if it overflows?
		if tags.has_key(node):
			print 'Duplicate key in tag table:', `node`
		file, offset = map_offset(offset, indirlist)
		tags[node] = file, offset, line
	#
	return tags


# Return true if buf[i:] begins with a label, after lower case conversion.
# The label argument must be in lower case.
#
def labelmatch(buf, i, label):
	return string.lower(buf[i:i+len(label)]) == label


# Parse the indirection list.
# Return a list of (filename, offset) pairs ready for use.
#
def parse_indirlist(buf):
	list = []
	findindir = regexp.compile('^(.+):[ \t]*([0-9]+)$').match
	i = 0
	while 1:
		match = findindir(buf, i)
		if not match:
			break
		(a,b), (a1,b1), (a2,b2) = match
		file = buf[a1:b1]
		offset = eval(buf[a2:b2]) # XXX What if this gets overflow?
		list.append((file, offset))
		i = b
	return list


# Map an offset through the indirection list.
# Return (filename, new_offset).
# If the list is empty, return the given offset and an empty file name.
#
def map_offset(offset, indirlist):
	if not indirlist:
		return '', offset
	#
	# XXX This could be done more elegant.
	#
	filex, offx = indirlist[0]
	for i in range(len(indirlist)):
		file1, off1 = indirlist[i]
		if i+1 >= len(indirlist):
			file2, off2 = '', 0x7fffffff
		else:
			file2, off2 = indirlist[i+1]
		if off1 <= offset < off2:
			# Add offx+2 to compensate for extra header.
			# No idea whether this is always correct.
			return file1, offset-off1 + offx+2
	#
	# XXX Shouldn't get here.
	#
	print 'Oops, map_offset fell through'
	return '', offset # Not likely to get good results
