Initial revision
diff --git a/Lib/linecache.py b/Lib/linecache.py
new file mode 100644
index 0000000..4355f19
--- /dev/null
+++ b/Lib/linecache.py
@@ -0,0 +1,71 @@
+# Cache lines from files.
+
+import os
+from stat import *
+
+def getline(filename, lineno):
+	lines = getlines(filename)
+	if 1 <= lineno <= len(lines):
+		return lines[lineno-1]
+	else:
+		return ''
+
+
+# The cache
+
+cache = {} # The cache
+
+
+# Clear the cache entirely
+
+def clearcache():
+	global cache
+	cache = {}
+
+
+# Get the lines for a file from the cache.
+# Update the cache if it doesn't contain an entry for this file already.
+
+def getlines(filename):
+	if cache.has_key(filename):
+		return cache[filename][2]
+	else:
+		return updatecache(filename)
+
+
+# Discard cache entries that are out of date.
+# (This is not checked upon each call
+
+def checkcache():
+	for filename in cache.keys():
+		size, mtime, lines = cache[filename]
+		try:	stat = os.stat(filename)
+		except os.error:
+			del cache[filename]
+			continue
+		if size <> stat[ST_SIZE] or mtime <> stat[ST_MTIME]:
+			del cache[filename]
+
+
+# Update a cache entry and return its list of lines.
+# If something's wrong, print a message, discard the cache entry,
+# and return an empty list.
+
+def updatecache(filename):
+	try:	del cache[filename]
+	except KeyError: pass
+	try:	stat = os.stat(filename)
+	except os.error, msg:
+		if filename[0] + filename[-1] <> '<>':
+			print '*** Cannot stat', filename, ':', msg
+		return []
+	try:
+		fp = open(filename, 'r')
+		lines = fp.readlines()
+		fp.close()
+	except IOError, msg:
+		print '*** Cannot open', filename, ':', msg
+		return []
+	size, mtime = stat[ST_SIZE], stat[ST_MTIME]
+	cache[filename] = size, mtime, lines
+	return lines
diff --git a/Lib/pdb.py b/Lib/pdb.py
new file mode 100755
index 0000000..259c770
--- /dev/null
+++ b/Lib/pdb.py
@@ -0,0 +1,396 @@
+# pdb.py -- finally, a Python debugger!
+
+# To do:
+# - Keep a list of exceptions trapped (default only KeyboardInterrupt?)
+# - A blank line should repeat the previous command, not execute 'next'
+# - Don't line-trace functions (or at least files) without breakpoints
+# - Better handling of call/return events
+# - It should be possible to intercept KeyboardInterrupt completely
+# - Where and when to stop exactly when 'next' encounters a return?
+#   (should be level-based -- don't trace anything deeper than current)
+# - Show stack traces upside-down (like dbx/gdb)
+
+import string
+import sys
+import linecache
+
+
+# A generic class to build command interpreters
+
+PROMPT = '(Cmd) '
+IDENTCHARS = string.letters + string.digits + '_'
+
+class Cmd:
+	def init(self):
+		self.prompt = PROMPT
+		self.identchars = IDENTCHARS
+		return self
+	def commandloop(self):
+		try:
+			self.innerloop()
+		except EOFError:
+			pass
+	def innerloop(self):
+		while 1:
+			line = string.strip(self.getline())
+			self.execline(line)
+	def execline(self, line):
+		i, n = 0, len(line)
+		while i < n and line[i] in self.identchars: i = i+1
+		cmd, arg = line[:i], string.strip(line[i:])
+		if cmd == '':
+			self.default(line)
+		else:
+			try:
+				func = eval('self.do_' + cmd)
+			except AttributeError:
+				self.default(line)
+				return
+			func(arg)
+	def getline(self):
+		return raw_input(self.prompt)
+	def default(self, line):
+		print '*** Unknown syntax:', line
+	def do_help(self, arg):
+		if arg:
+			# XXX check arg syntax
+			try:
+				func = eval('self.help_' + arg)
+			except:
+				print '*** No help on', `arg`
+				return
+			func()
+		else:
+			import getattr
+			names = getattr.dir(self)
+			cmds = []
+			for name in names:
+				if name[:3] == 'do_':
+					cmds.append(name[3:])
+			print cmds
+
+
+# A specialization of Cmd for use by the debugger
+
+PdbDone = 'PdbDone' # Exception used internally
+PdbQuit = 'PdbQuit' # Exception to just give up
+
+class Pdb(Cmd):
+	
+	def init(self):
+		self = Cmd.init(self)
+		self.prompt = '(Pdb) '
+		self.reset()
+		self.breaks = {}
+		return self
+	
+	def reset(self):
+		self.whatnext = ''
+		self.botframe = None
+		self.forget()
+	
+	def forget(self):
+		self.curframe = self.topframe = None
+		self.stack = []
+		self.lineno = None
+	
+	def setup(self, frame):
+		self.curframe = self.topframe = frame
+		self.stack = []
+		self.lineno = None
+		self.stopframe = None
+		self.whatnext = ''
+	
+	def run(self, cmd):
+		if cmd[-1:] != '\n': cmd = cmd + '\n'
+		self.reset()
+		sys.trace = self.dispatch
+		try:
+			exec(cmd)
+		except PdbQuit:
+			pass
+		finally:
+			sys.trace = None
+			del sys.trace
+			self.reset()
+	
+	def dispatch(self, frame, where, arg):
+		if self.whatnext == 'quit':
+			return
+		if self.botframe is None:
+			self.botframe = frame
+		if where == 'exception':
+			stop = 1
+		elif self.whatnext == 'continue':
+			stop = 0
+		elif self.whatnext == 'next':
+			stop = (frame == self.stopframe)
+		else:
+			stop = 1
+		if not stop:
+			# Check breakpoints only
+			filename = frame.f_code.co_filename
+			if not self.breaks.has_key(filename):
+				return self.dispatch
+			lineno = frame.f_lineno
+			if lineno not in self.breaks[filename]:
+				return self.dispatch
+		if where == 'call':
+			print 'call arguments:', arg
+		elif where == 'return':
+			print 'return value:', arg
+		elif where == 'exception':
+			print 'exception:', arg[:2]
+		elif where != 'line':
+			print 'unknown trace type:', `where`
+		self.setup(frame)
+		try:
+			self.whatnext = self.commandloop()
+		finally:
+			self.forget()
+		if self.whatnext == 'quit':
+			raise PdbQuit
+		if self.whatnext == 'next':
+			if where != 'return':
+				self.stopframe = frame
+			else:
+				self.whatnext = 'step'
+		return self.dispatch
+	
+	def commandloop(self):
+		self.printwhere(self.curframe)
+		try:
+			self.innerloop()
+		except EOFError:
+			self.do_next('')
+		except PdbDone, msg:
+			return msg
+	
+	def default(self, line):
+		if not line:
+			self.do_next('')
+		else:
+			if line[0] == '!': line = line[1:]
+			try:
+				exec(line + '\n', \
+					self.curframe.f_globals, \
+					self.curframe.f_locals)
+			except:
+				print '***', sys.exc_type + ':',
+				print `sys.exc_value`
+	
+	do_h = Cmd.do_help
+	
+	def do_break(self, arg):
+		if not arg:
+			print self.breaks # XXX
+			return
+		try:
+			lineno = int(eval(arg))
+		except:
+			print '*** Error in argument:', `arg`
+			return
+		filename = self.curframe.f_code.co_filename
+		line = linecache.getline(filename, lineno)
+		if not line:
+			print '*** That line does not exist!'
+			return
+		if not self.breaks.has_key(filename):
+			self.breaks[filename] = []
+		list = self.breaks[filename]
+		if lineno in list:
+			print '*** There is already a break there!'
+			return
+		list.append(lineno)
+	do_b = do_break
+	
+	def do_clear(self, arg):
+		if not arg:
+			self.breaks = {}
+			print 'All breaks cleared!'
+			return
+		try:
+			lineno = int(eval(arg))
+		except:
+			print '*** Error in argument:', `arg`
+			return
+		filename = self.curframe.f_code.co_filename
+		try:
+			self.breaks[filename].remove(lineno)
+			if self.breaks[filename] == []:
+				del self.breaks[filename]
+		except (ValueError, KeyError):
+			print '*** There is no break there!'
+			return
+	
+	def do_where(self, arg):
+		self.printtb()
+	do_w = do_where
+	
+	def do_up(self, arg):
+		if not self.stack: print '*** Top'
+		else:
+			self.curframe = self.stack[-1]
+			self.lineno = None
+			del self.stack[-1]
+			self.printwhere(self.curframe)
+	do_u = do_up
+	
+	def do_down(self, arg):
+		if self.curframe == self.botframe or \
+			not self.curframe.f_back: print '*** Bottom'
+		else:
+			self.stack.append(self.curframe)
+			self.curframe = self.curframe.f_back
+			self.lineno = None
+			self.printwhere(self.curframe)
+	do_d = do_down
+	
+	def do_step(self, arg):
+		raise PdbDone, 'step'
+	do_s = do_step
+	
+	def do_next(self, arg):
+		raise PdbDone, 'next'
+	do_n = do_next
+	
+	def do_continue(self, arg):
+		raise PdbDone, 'continue'
+	do_c = do_cont = do_continue
+	
+	def do_quit(self, arg):
+		raise PdbDone, 'quit'
+	do_q = do_quit
+	
+	def do_list(self, arg):
+		last = None
+		if arg:
+			try:
+				x = eval(arg, {}, {})
+				if type(x) == type(()):
+					first, last = x
+					first = int(first)
+					last = int(last)
+					if last < first:
+						# Assume it's a count
+						last = first + last
+				else:
+					first = int(x)
+			except:
+				print '*** Error in argument:', `arg`
+				return
+		elif self.lineno is None:
+			first = max(1, self.curframe.f_lineno - 5)
+		else:
+			first = self.lineno + 1
+		if last is None:
+			last = first + 10
+		filename = self.curframe.f_code.co_filename
+		if self.breaks.has_key(filename):
+			breaklist = self.breaks[filename]
+		else:
+			breaklist = []
+		try:
+			for lineno in range(first, last+1):
+				line = linecache.getline(filename, lineno)
+				if not line:
+					print '[EOF]'
+					break
+				else:
+					s = string.rjust(`lineno`, 3)
+					if len(s) < 4: s = s + ' '
+					if lineno in breaklist: s = s + 'B'
+					else: s = s + ' '
+					if lineno == self.curframe.f_lineno:
+						s = s + '->'
+					print s + '\t' + line,
+					self.lineno = lineno
+		except KeyboardInterrupt:
+			pass
+	do_l = do_list
+
+	# Print a traceback starting at a given stack frame
+	# Note that it is printed upside-down with respect
+	# to the orientation suggested by the up/down commands.
+	# This is consistent with gdb.
+	def printtb(self):
+		list = []
+		frame = self.topframe
+		while frame:
+			list.append(frame)
+			if frame is self.botframe: break
+			frame = frame.f_back
+		list.reverse()
+		for frame in list:
+			self.printwhere(frame)
+	
+	def printwhere(self, frame):
+		if frame is self.curframe: print '->',
+		code = frame.f_code
+		filename = code.co_filename
+		lineno = frame.f_lineno
+		print filename + '(' + `lineno` + ')',
+		line = linecache.getline(filename, lineno)
+		if line: print string.strip(line),
+		print
+
+
+# --------------------- testing ---------------------
+
+# The Ackermann function -- a highly recursive beast
+cheat = 0
+cache = {}
+def ack(x, y):
+	key = `(long(x), long(y))`
+	if cache.has_key(key):
+		res = cache[key]
+	else:
+		if x == 0:
+			res = 1L
+		elif y == 0:
+			if x == 1:
+				res = 2L
+			else:
+				res = 2L + x
+		elif y == 1 and cheat >= 1:
+			res = 2L * x
+		elif y == 2 and cheat >= 2:
+			res = pow(2L, x)
+		else:
+			res = ack(ack(x-1, y), y-1)
+		cache[key] = res
+	return res
+
+def foo(n):
+	print 'foo', n
+	x = bar(n*2)
+	print 'bar returned', x
+	return
+
+def bar(a):
+	print 'bar', a
+	return a*10
+
+def test():
+	linecache.checkcache()
+	Pdb().init().run('foo(12)')
+
+
+# --------------------- main ---------------------
+
+import os
+
+def main():
+	if sys.argv[1:]:
+		file = sys.argv[1]
+		head, tail = os.path.split(file)
+		if tail[-3:] != '.py':
+			print 'Sorry, file arg must be a python module'
+			print '(i.e., must end in \'.py\')'
+			# XXX Or we could copy it to a temp file
+			sys.exit(2)
+		del sys.argv[0]
+		sys.path.insert(0, head)
+		Pdb().init().run('import ' + tail[:-3])
+	else:
+		Pdb().init().run('')