import sys import unittest BEGIN_ACTIVE_FUNC = "\x80" BEGIN_NEUTRAL_FUNC = "\x81" END_FUNC = "\x8f" END_ARG = "\x8e" SEGMENT_GAP = "\xff" class TracError(Exception): pass def list_get(list_, index, default=""): try: return list_[index] except: return default def scan_char(list_, pos): return list_get(list_, pos) def scan_chars(list_, pos, n): chars = [] for i in range(n): c = scan_char(list_, pos + i) if c: chars.append(c) return "".join(chars) class Processor(object): def __init__(self, program=""): self.work = [] # workspace containing current TRAC program self.sp = 0 # position of scanning pointer in workspace self.forms = {} # key-value storage for program variables self.output = "" # last string printed to output by ps for unit testing self.trace = True # flag for printing trace results of function evaluation self.primitives = {} # dictionary containing bound methods for TRAC primitives self.initialize(program) def tn(self, args): self.trace = True return "" def tf(self, args): self.trace = False return "" def ds(self, args): key = list_get(args, 0) value = list_get(args, 1) self.forms[key] = value return "" def ps(self, args): try: s = list_get(args, 0) print s self.output = s except: pass return "" def ad(self, args): try: num1 = int(list_get(args, 0)) num2 = int(list_get(args, 1)) return str(num1 + num2) except: return "" def su(self, args): try: num1 = int(list_get(args, 0)) num2 = int(list_get(args, 1)) return str(num1 - num2) except: return "" def ml(self, args): try: num1 = int(list_get(args, 0)) num2 = int(list_get(args, 1)) return str(num1 * num2) except: return "" def dv(self, args): try: num1 = int(list_get(args, 0)) num2 = int(list_get(args, 1)) return str(num1 / num2) except: return "" def eq(self, args): try: s1 = list_get(args, 0) s2 = list_get(args, 1) eq_result = list_get(args, 2) neq_result = list_get(args, 3) if s1 == s2: return eq_result else: return neq_result except: return "" def ss(self, args): try: form_key = args.pop(0) form = self.forms[form_key] form_marked = form for i in range(len(args)): arg = args[i] marker = "%s%s" % (SEGMENT_GAP, chr(i)) form_marked = form_marked.replace(arg, marker) self.forms[form_key] = form_marked form_list = [] form_list += form_marked #print "ss: %s" % (form_list) return "" except: return "" def cl(self, args): try: form_key = args.pop(0) form = self.forms[form_key] form_processed = form for i in range(len(args)): arg = args[i] marker = "%s%s" % (SEGMENT_GAP, chr(i)) form_processed = form_processed.replace(marker, arg) return form_processed except: return "" def initialize(self, program=""): self.forms = {} self.work = [] self.reset() self.work += program self.primitives = {"ds":self.ds, \ "ps":self.ps, \ "ss":self.ss, \ "cl":self.cl, \ "ad":self.ad, \ "su":self.su, \ "ml":self.ml, \ "dv":self.dv, \ "tn":self.tn, \ "tf":self.tf, \ "eq":self.eq \ } def run(self): args = [] handler = self.scan_next_char while handler: try: next_handler, args = handler(args) except TracError, e: sys.stderr.write("TracError: %s\n" % e ) next_handler, args = self.reset, [] handler = next_handler def scan_next_char(self, args): # Rule 1 args = [] #self.db("scan_next_char") c = scan_char(self.work, self.sp) if c: if c == '(': handler = self.handle_begin_paren elif c in "\n\r\t": handler = self.handle_tab_return elif c == ',': handler = self.handle_comma elif c == '#' and scan_chars(self.work, self.sp, 2) == '#(': handler = self.handle_begin_active_func elif c == '#' and scan_chars(self.work, self.sp, 3) == '##(': handler = self.handle_begin_neutral_func elif c == '#': handler = self.handle_sharp_sign elif c == ')': handler = self.handle_end_paren else: args.append(c) handler = self.handle_char else: self.db("exit") print "Forms: %s" % (self.forms) print "Output: [%s]" % (self.output) handler = None return handler, args def handle_begin_paren(self, args): # Rule 2 args = [] nested_count = 1 chars = [] matched = False del self.work[self.sp] c = scan_char(self.work, self.sp) while c and not matched: if c == ')': nested_count -= 1 if nested_count == 0: matched = True break if not matched: if c == '(': nested_count += 1 chars.append(c) self.sp += 1 c = scan_char(self.work, self.sp) if matched: del self.work[self.sp] else: raise TracError, "%s: can't find matching end parenthesis" %("handle_begin_paren") return self.scan_next_char, [] def handle_tab_return(self, args): # Rule 3 args = [] del self.work[self.sp] self.sp -= 1 return self.inc_scan_pointer_continue, args def handle_comma(self, args): # Rule 4 args = [] self.work[self.sp] = END_ARG return self.inc_scan_pointer_continue, args def handle_begin_active_func(self, args): # Rule 5 args = [] del self.work[self.sp:self.sp + 2] self.work.insert(self.sp, BEGIN_ACTIVE_FUNC) self.sp += 1 return self.scan_next_char, args def handle_begin_neutral_func(self, args): # Rule 6 args = [] del self.work[self.sp:self.sp + 3] self.work.insert(self.sp, BEGIN_NEUTRAL_FUNC) self.sp += 1 return self.scan_next_char, args def handle_sharp_sign(self, args): # Rule 7 args = [] return self.inc_scan_pointer_continue, args def handle_end_paren(self, args): # Rule 8 #self.db("end_paren_0") args = [] self.work[self.sp] = END_FUNC func_begin = self.get_func_begin() func_result = self.eval_func(func_begin) func_marker = self.work[func_begin] args.append(func_begin) if func_result == "": handler = self.handle_null_func_result # Rule 10 elif func_marker == BEGIN_ACTIVE_FUNC: args.append(func_result) handler = self.handle_active_func_result # Rule 11 elif func_marker == BEGIN_NEUTRAL_FUNC: args.append(func_result) handler = self.handle_neutral_func_result # Rule 12 else: raise TracError, "%s: invalid func_marker" %("handle_end_paren") #self.db("end_paren_1") return handler, args def get_func_begin(self): pos = self.sp - 1 c = self.work[pos] while c: if c == BEGIN_ACTIVE_FUNC or c == BEGIN_NEUTRAL_FUNC: break pos -= 1 if pos >= 0: c = self.work[pos] else: raise TracError, "%s: can't find begin function marker" %("get_func_begin") return pos def get_func_end(self, func_begin): pos = func_begin c = self.work[pos] while c: if c == END_FUNC: break pos += 1 c = self.work[pos] return pos def get_func_args(self, func_begin): args = [] cur_arg = [] pos = func_begin c = self.work[pos] db = [] while c: db.append(c) if c == BEGIN_ACTIVE_FUNC or c == BEGIN_NEUTRAL_FUNC: pass elif c == END_ARG or c == END_FUNC: arg = "".join(cur_arg) args.append(arg) cur_arg = [] else: cur_arg.append(c) if c != END_FUNC: pos += 1 c = self.work[pos] db = [] else: break return args def eval_func(self, func_begin): result = "" try: args = self.get_func_args(func_begin) func_name = args[0] primitive = self.primitives.get(func_name, None) if primitive: result = primitive(args[1:]) if self.trace: print "eval_func: %s %s -> [%s]" % (func_name, args[1:], result) except Exception, e: raise TracError, "%s: failed - %s" %("eval_func", e) return result def handle_char(self, args): # Rule 9 c = args[0] args = [] return self.inc_scan_pointer_continue, args def handle_null_func_result(self, args): # Rule 10 return self.handle_func_cleanup, args def handle_active_func_result(self, args): # Rule 11 func_begin = args[0] func_result = args[1] args = [] self.work[self.sp+1:self.sp+1] += func_result args.append(func_begin) #self.db("handle_active_func_result") return self.handle_func_cleanup, args def handle_neutral_func_result(self, args): # Rule 12 func_begin = args[0] func_result = args[1] args = [] self.work[self.sp+1:self.sp+1] += func_result func_end = self.sp del self.work[func_begin:func_end + 1] self.sp = func_begin + len(func_result) - 1 #self.db("handle_neutral_func_result") return self.inc_scan_pointer_continue, args def handle_func_cleanup(self, args): # Rule 13 if args: func_begin = args[0] func_end = self.get_func_end(func_begin) args = [] del self.work[func_begin:func_end + 1] self.sp = func_begin - 1 #self.db("handle_func_cleanup") return self.inc_scan_pointer_continue, args def reset(self, args=[]): # Rule 14 args = [] self.work = [] self.sp = 0 return self.scan_next_char, args def inc_scan_pointer_continue(self, args): # Rule 15 args = [] self.sp += 1 return self.scan_next_char, args def db(self, msg="db"): print "%s: %s SP:%d %s" % (msg, self.work[0:self.sp], self.sp, self.work[self.sp:]) class TestTrac(unittest.TestCase): def setUp(self): pass def __test(self, program, output): self.processor = Processor() self.processor.initialize(program) self.processor.run() self.assertEqual(self.processor.output, output) def test_1_ps(self): self.__test("#(ps,Hello world)", "Hello world") def test_2_equal(self): self.__test("#(ps,#(eq,Cat,Cat,equal,not equal))", "equal") def test_3_not_equal(self): self.__test("#(ps,#(eq,Cat,Dog,equal,not equal))", "not equal") def test_4_ds(self): self.__test("#(ds,AA,Cat)#(ps,#(cl,AA))", "Cat") def test_5_protect_parens(self): self.__test("#(ds,AA,Cat)#(ds,BB,(#(cl,AA)))#(ps,(#(cl,BB)))", "#(cl,BB)") def test_6_neutral_func(self): self.__test("#(ds,AA,Cat)#(ds,BB,(#(cl,AA)))#(ps,##(cl,BB))", "#(cl,AA)") def test_7_indirection(self): self.__test("#(ds,AA,Cat)#(ds,BB,(#(cl,AA)))#(ps,#(cl,BB))", "Cat") def test_8_ss(self): self.__test("#(ds,AA,Hello X)#(ss,AA,X)#(ps,#(cl,AA,world))", "Hello world") def test_9_factorial(self): self.__test(""" #(ds,Factorial,(#(eq,X,1,1,(#(ml,X,#(cl,Factorial,#(su,X,1))))))) #(ss,Factorial,X) #(ps,#(cl,Factorial,5)) """, "120") if __name__ == "__main__": print __file__ unittest.main()