aboutsummaryrefslogtreecommitdiff
path: root/lib/Tcl.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Tcl.py')
-rw-r--r--lib/Tcl.py421
1 files changed, 421 insertions, 0 deletions
diff --git a/lib/Tcl.py b/lib/Tcl.py
new file mode 100644
index 0000000..9d45e37
--- /dev/null
+++ b/lib/Tcl.py
@@ -0,0 +1,421 @@
+# An emulator for John Ousterhout's 'Tcl' language in Python (wow!).
+# Currently only the most basic commands are implemented.
+#
+# Design choices:
+#
+# - Names used for functions are not exactly those used by C Tcl.
+# In Python, names without 'Tcl_' prefix are acceptable because
+# names are less global than in C (and often they are prefixed
+# with a module name anyway). Parameter conventions also differ.
+#
+# - The Tcl Interpreter type is implemented using a Python class.
+# Almost all functions with an Interpreter as first parameter are
+# methods of this class.
+# Applications can create derived classes to add additional commands
+# or to override specific internal functions.
+#
+# - Tcl errors are mapped to Python exceptions.
+# (I bet Ousterhout would have done the same in a language with
+# a proper exception mechanism).
+#
+# - Tcl expressions are evaluated by Python's built-in function eval().
+# This makes Python Tcl scripts incompatible with C Tcl scripts,
+# but is the only sensible solution for a quick-and-dirty version.
+# It also makes an escape to Python possible.
+#
+# - The Backslash function interprets \<newline>, since it
+# can return a string instead of a character.
+
+
+from TclUtil import *
+
+
+# Exceptions used to signify 'break' and 'continue'
+
+TclBreak = 'TclBreak'
+TclContinue = 'TclContinue'
+TclReturn = 'TclReturn'
+
+
+class CmdBuf():
+ #
+ def Create(buffer):
+ buffer.string = ''
+ return buffer
+ #
+ def Assemble(buffer, str):
+ buffer.string = buffer.string + str
+ if buffer.string[-1:] = '\n':
+ i, end = 0, len(buffer.string)
+ try:
+ while i < end:
+ list, i = FindNextCommand( \
+ buffer.string, i, end, 0)
+ except TclMatchingError:
+ return ''
+ except TclSyntaxError:
+ pass # Let Eval() return the error
+ ret = buffer.string
+ buffer.string = ''
+ return ret
+ else:
+ return ''
+
+
+class _Frame():
+ def Create(frame):
+ frame.locals = {}
+ return frame
+
+class _Proc():
+ #
+ def Create(proc, (interp, args, body)):
+ proc.interp = interp
+ proc.args = SplitList(args) # Do this once here
+ proc.body = body
+ return proc
+ #
+ def Call(proc, argv):
+ if len(argv) <> len(proc.args)+1:
+ raise TclRuntimeError, \
+ 'wrong # args to proc "' + \
+ argv[0] + '"'
+ # XXX No defaults or variable length 'args' yet
+ frame = _Frame().Create()
+ for i in range(len(proc.args)):
+ frame.locals[proc.args[i]] = argv[i+1]
+ proc.interp.stack.append(frame)
+ try:
+ value = proc.interp.Eval(proc.body)
+ except TclReturn, value:
+ pass
+ del proc.interp.stack[-1:]
+ return value
+
+
+import regexp
+_expand_prog = regexp.compile('([^[$\\]+|\n)*')
+del regexp
+
+class Interpreter():
+ #
+ def Create(interp):
+ interp.globals = {}
+ interp.commands = {}
+ interp.stack = []
+ interp.commands['break'] = interp.BreakCmd
+ interp.commands['concat'] = interp.ConcatCmd
+ interp.commands['continue'] = interp.ContinueCmd
+ interp.commands['echo'] = interp.EchoCmd
+ interp.commands['eval'] = interp.EvalCmd
+ interp.commands['expr'] = interp.ExprCmd
+ interp.commands['for'] = interp.ForCmd
+ interp.commands['glob'] = interp.GlobCmd
+ interp.commands['global'] = interp.GlobalCmd
+ interp.commands['if'] = interp.IfCmd
+ interp.commands['index'] = interp.IndexCmd
+ interp.commands['list'] = interp.ListCmd
+ interp.commands['proc'] = interp.ProcCmd
+ interp.commands['rename'] = interp.RenameCmd
+ interp.commands['return'] = interp.ReturnCmd
+ interp.commands['set'] = interp.SetCmd
+ return interp
+ #
+ def Delete(interp):
+ #
+ # Only break circular references here;
+ # most things will be garbage-collected.
+ #
+ for name in interp.commands.keys():
+ del interp.commands[name]
+ #
+ def CreateCommand(interp, (name, proc)):
+ interp.commands[name] = proc
+ #
+ def DeleteCommand(interp, (name)):
+ del interp.commands[name]
+ #
+ # Local variables are maintained on the stack.
+ # A local variable with value "None" is a dummy
+ # meaning that the corresponding global variable
+ # should be used.
+ #
+ def GetVar(interp, varName):
+ dict = interp.globals
+ if interp.stack:
+ d = interp.stack[-1:][0].locals
+ if d.has_key(varName) and d[varName] = None:
+ pass
+ else:
+ dict = d
+ if not dict.has_key(varName):
+ raise TclRuntimeError, \
+ 'Variable "' + varName + '" not found'
+ return dict[varName]
+ #
+ def SetVar(interp, (varName, newValue)):
+ dict = interp.globals
+ if interp.stack:
+ d = interp.stack[-1:][0].locals
+ if d.has_key(varName) and d[varName] = None:
+ pass
+ else:
+ dict = d
+ dict[varName] = newValue
+ #
+ def Expand(interp, (str, i, end)):
+ if end <= i: return ''
+ if str[i] = '{' and str[end-1] = '}':
+ return str[i+1:end-1]
+ if str[i] = '"' and str[end-1] = '"':
+ i, end = i+1, end-1
+ result = ''
+ while i < end:
+ c = str[i]
+ if c = '\\':
+ x, i = Backslash(str, i, end)
+ result = result + x
+ elif c = '[':
+ j = BalanceBrackets(str, i, end)
+ x = interp.EvalBasic(str, i+1, j-1, 1)
+ result = result + x
+ i = j
+ elif c = '$':
+ i = i+1
+ j = FindVarName(str, i, end)
+ name = str[i:j]
+ i = j
+ if not name:
+ result = result + '$'
+ else:
+ if name[:1] = '{' and name[-1:] = '}':
+ name = name[1:-1]
+ result = result + interp.GetVar(name)
+ else:
+ j = _expand_prog.exec(str, i)
+ j = min(j, end)
+ result = result + str[i:j]
+ i = j
+ return result
+ #
+ def EvalBasic(interp, (str, i, end, bracketed)):
+ result = ''
+ while i < end:
+ indexargv, i = FindNextCommand( \
+ str, i, end, bracketed)
+ if indexargv:
+ argv = []
+ for x, y in indexargv:
+ arg = interp.Expand(str, x, y)
+ argv.append(arg)
+ name = argv[0]
+ if not interp.commands.has_key(name):
+ raise TclRuntimeError, \
+ 'Command "' + name + \
+ '" not found'
+ result = interp.commands[name](argv)
+ return result
+ #
+ def Eval(interp, str):
+ return interp.EvalBasic(str, 0, len(str), 0)
+ #
+ def ExprBasic(interp, (str, begin, end)):
+ expr = interp.Expand(str, begin, end)
+ i = SkipSpaces(expr, 0, len(expr))
+ expr = expr[i:]
+ try:
+ return eval(expr, {})
+ except (NameError, TypeError, RuntimeError, EOFError), msg:
+ import sys
+ raise TclRuntimeError, sys.exc_type + ': ' + msg
+ #
+ def Expr(interp, str):
+ return interp.ExprBasic(str, 0, len(str))
+ #
+ # The rest are command implementations
+ #
+ def BreakCmd(interp, argv):
+ if len(argv) <> 1:
+ raise TclRuntimeError, 'usage: break'
+ raise TclBreak
+ #
+ def ConcatCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: concat arg ...'
+ return Concat(argv[1:])
+ #
+ def ContinueCmd(interp, argv):
+ if len(argv) <> 1:
+ raise TclRuntimeError, 'usage: continue'
+ raise TclContinue
+ #
+ def EchoCmd(interp, argv):
+ for arg in argv[1:]: print arg,
+ print
+ return ''
+ #
+ def EvalCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: eval arg [arg ...]'
+ str = Concat(argv[1:])
+ return interp.Eval(str)
+ #
+ def ExprCmd(interp, argv):
+ if len(argv) <> 2:
+ raise TclRuntimeError, 'usage: expr expression'
+ expr = argv[1]
+ result = interp.Expr(expr)
+ if type(result) <> type(''): result = `result`
+ return result
+ #
+ def ForCmd(interp, argv):
+ if len(argv) <> 5:
+ raise TclRuntimeError, \
+ 'usage: for start test next body'
+ x = interp.Eval(argv[1])
+ while interp.Expr(argv[2]):
+ try:
+ x = interp.Eval(argv[4])
+ except TclBreak:
+ break
+ except TclContinue:
+ pass
+ x = interp.Eval(argv[3])
+ return ''
+ #
+ def GlobCmd(interp, argv):
+ import macglob
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: glob pattern ...'
+ list = []
+ for pat in argv[1:]:
+ list = list + macglob.glob(pat)
+ if not list:
+ raise TclRuntimeError, 'no match for glob pattern(s)'
+ return BuildList(list)
+ #
+ def GlobalCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: global varname ...'
+ if not interp.stack:
+ raise TclRuntimeError, 'global used outside proc'
+ dict = interp.stack[-1:][0].locals
+ for name in argv[1:]:
+ dict[name] = None
+ return ''
+ #
+ def IfCmd(interp, argv):
+ argv = argv[:]
+ if len(argv) > 2 and argv[2] = 'then': del argv[2]
+ if len(argv) > 3 and argv[3] = 'else': del argv[3]
+ if not 3 <= len(argv) <= 4:
+ raise TclRuntimeError, \
+ 'usage: if test [then] trueBody [else] falseBody'
+ if interp.Expr(argv[1]):
+ return interp.Eval(argv[2])
+ if len(argv) > 3:
+ return interp.Eval(argv[3])
+ return ''
+ #
+ def IndexCmd(interp, argv):
+ if len(argv) <> 3:
+ raise TclRuntimeError, 'usage: index value index'
+ import string
+ try:
+ index = string.atoi(argv[2])
+ if index < 0: raise string.atoi_error
+ except string.atoi_error:
+ raise TclRuntimeError, 'bad index: ' + argv[2]
+ list = SplitList(argv[1])
+ if index >= len(list): return ''
+ return list[index]
+ #
+ def ListCmd(interp, argv):
+ if len(argv) < 2:
+ raise TclRuntimeError, 'usage: list arg ...'
+ return BuildList(argv[1:])
+ #
+ def ProcCmd(interp, argv):
+ if len(argv) <> 4:
+ raise TclRuntimeError, 'usage: proc name args body'
+ x = _Proc().Create(interp, argv[2], argv[3])
+ interp.CreateCommand(argv[1], x.Call)
+ return ''
+ #
+ def RenameCmd(interp, argv):
+ if len(argv) <> 3:
+ raise TclRuntimeError, 'usage: rename oldName newName'
+ oldName, newName = argv[1], argv[2]
+ if not interp.commands.has_key(oldName):
+ raise TclRuntimeError, \
+ 'command "' + oldName + '" not found'
+ if newName: interp.commands[newName] = interp.commands[oldName]
+ del interp.commands[oldName]
+ return ''
+ #
+ def ReturnCmd(interp, argv):
+ if not 1 <= len(argv) <= 2:
+ raise TclRuntimeError, 'usage: return [arg]'
+ if len(argv) = 1: raise TclReturn, ''
+ raise TclReturn, argv[1]
+ #
+ def SetCmd(interp, argv):
+ n = len(argv)
+ if not 2 <= n <= 3:
+ raise TclRuntimeError, 'usage: set varname [newvalue]'
+ if n = 2: return interp.GetVar(argv[1])
+ interp.SetVar(argv[1], argv[2])
+ return ''
+
+
+# The rest are just demos:
+
+def MainLoop(interp):
+ buffer = CmdBuf().Create()
+ if not interp.globals.has_key('ps1'): interp.globals['ps1'] = '% '
+ if not interp.globals.has_key('ps2'): interp.globals['ps2'] = ''
+ psname = 'ps1'
+ while 1:
+ try:
+ line = raw_input(interp.globals[psname])
+ except (EOFError, KeyboardInterrupt):
+ print
+ break
+ line = buffer.Assemble(line + '\n')
+ if not line:
+ psname = 'ps2'
+ else:
+ psname = 'ps1'
+ try:
+ x = interp.Eval(line)
+ if x <> '': print 'Result:', `x`
+ except (TclRuntimeError, TclSyntaxError, \
+ TclMatchingError), msg:
+ print 'Error:', msg
+ except (TclBreak, TclContinue):
+ print 'Error: break or continue outside loop'
+ except TclReturn, value:
+ # Return outside proc returns to main loop
+ if value: print value
+
+
+the_interpreter = Interpreter().Create()
+
+def main():
+ MainLoop(the_interpreter)
+
+
+# XXX To do:
+# for proc: "args" and default arguments
+# More commands:
+# case
+# uplevel
+# info
+# string
+# list operations
+# error, catch
+# print
+# scan, format
+# source
+# history?
+# others?