git.fiddlerwoaroof.com
reader.py
ff4e0dfb
 from __future__ import print_function
 import sys
 import inspect
5267dfda
 import contextlib
 
 @contextlib.contextmanager
 def redir_stdout(new):
     old = sys.stdout
     sys.stdout = new
     yield
     sys.stdout = old
 
ff4e0dfb
 SPACE = None
 class _RUN: pass
 
 def read_word(fil):
     char = ' '
     buffer = []
     delim  = SPACE
     spaces = {' ', '\n', '\t'}
     while char != '':
         char = fil.read(1)
         if char == '':
             yield ''.join(buffer).strip()
             break
         buffer.append(char)
         if delim != SPACE and buffer[-len(delim):] == delim:
             delim = yield ''.join(buffer[:-len(delim)])
             buffer = []
         elif char in spaces:
             delim = yield ''.join(buffer[:-1]).strip()
             if char == '\n':
                 yield _RUN
             buffer = []
 
 def skip_run(reader):
     skipped = False
     r = reader.next()
     while r == _RUN:
         skipped = skipped or r == _RUN
         #print(r, end=':')
         r = reader.next()
         #print(r, end=' ')
     #print('out')
     return r, skipped
 
 import collections
 import traceback
 class SymbolDict(collections.MutableMapping):
     def __init__(self, *args, **kwargs):
         self.ctr = 0
         self.items_ = set()
         self.dct = dict(*args, **kwargs)
 
     def __getitem__(self, key):
         if self.dct[key] != []:
             return self.dct[key][-1][1]
         else:
             raise KeyError("No such key '%s'" % key)
     def __setitem__(self, key, value):
         self.ctr += 1
         self.items_.add(key)
         self.dct.setdefault(key, []).append((self.ctr, value))
     def __delitem__(self, key):
         state = self.dct[key][-1][0]
         for a in self.dct.keys():
             while self.dct[a] and self.dct[a][-1][0] >= state:
                 self.dct[a].pop()
             if self.dct[a] == []: self.dct.pop(a)
     forget = __delitem__
     def __iter__(self):
         return (x for x in self.dct.keys())
     def __len__(self):
         return len(self.dct)
 
 import random
 
 class Reader(object):
     symbols = SymbolDict()
 
     def rev_pctr(self, stack, rstack):
         num = stack.pop()
         self.program_ctr -= num
 
     def adv_pctr(self, stack, rstack):
         num = stack.pop()
         self.program_ctr += num
 
     def adv_pctr0(self, stack, rstack):
         #print('adv0 -->', stack, rstack)
         num = stack.pop()
         cond = stack.pop()
         if cond == 0:
             stack.append(num)
             self.adv_pctr(stack, rstack)
 
     def save_program_ctr(self):
         self.pcs.append(self.program_ctr)
     def restore_program_ctr(self):
         self.program_ctr = self.pcs.pop()
     def rollback_program(self):
         self.restore_program_ctr()
         del self.program[self.program_ctr:]
     def splice_program(self, words):
         begin = self.peek_saved_program_ctr()
         end = self.program_ctr + 1
         self.program[begin:end] = words
     def peek_saved_program_ctr(self):
         return self.pcs[-1]
     def drop_saved_program_ctr(self):
         return self.pcs.pop()
 
     def if_(self, stack, rstack=None):
         def if_reader(gen):
             counter = 0
             if_clause = []
             else_clause = []
             cur_clause = if_clause
             for word in gen:
                 if word == 'if':
                     cur_clause.extend(if_reader(gen))
                     continue
                 elif word == 'else':
                     cur_clause = else_clause
                     continue
                 elif word == 'then':
                     else_clause.extend(['nop'])
                     break
                 cur_clause.extend(self.expand_word(word))
             if_clause.extend(['%d' % len(else_clause), 'adv'])
             if_clause.insert(0, '%d' % len(if_clause))
             if_clause.insert(1, 'adv0')
             result = if_clause + else_clause
             return result
 
         ## Below we replace the if statement with a call to lower-level primitives
         ## i.e. if is implemented as a kind of macro.
         self.save_program_ctr()
         self.program_ctr += 1 # since the program counter doesn't update until after the yield
         gen = self.read_word()
         self.splice_program(if_reader(gen))
         self.restore_program_ctr()
         self.program_ctr -= 1 # in order to pick up the beginning of the if statement
 
     def words(self, stack, rstack=None):
         words = sorted(self.symbols.keys())
         mlen = max(len(x) for x in words)
         c = 80 // (mlen + 2)
         while words:
             head, words = words[:c], words[c:]
5267dfda
             print(' '.join(x.center(mlen) for x in head), file=self.out)
ff4e0dfb
 
     def forget(self, stack, rstack=None):
         self.program_ctr += 1 # since the program counter doesn't update until after the yield
         name, skipped = skip_run(self.read_word())
         self.symbols.forget(name)
         if skipped: self.inp.append([_RUN])
 
     def colon(self, stack, rstack=None):
         self.program_ctr += 1 # since the program counter doesn't update until after the yield
         gen = self.read_word()
         name = gen.next()
         word = ' '
         conts = []
         while word != ';':
             word = gen.next()
             if word == _RUN: continue
             conts.append(word)
         conts.pop() # pop off the semicolon
 
         self.compile(name, filter(None,conts))
         #print(' >name', name, ' >symbols["%s"]' % name, self.symbols[name.lower()])
 
     def expand_word(self, word):
         if word in self.sym_words:
             outp = [self.expand_word(w) for w in self.sym_words[word][:]]
             return sum(outp, [])
         else:
             return [word]
 
     def compile(self, name, words):
         if hasattr(words, 'split'): words = words.split()
         self.sym_words[name] = words[:]
         @self.add_symbol(name)
         def newfun(stack, rstack=None):
             self.save_program_ctr()
             self.splice_program(words[:])
             self.program_ctr -= 1
         newfun.__name__ = '_forth_%s' % name
     def prog_print(self, stack, rstack=None):
5267dfda
         print('<%d>' % len(self.program), end=' ', file=self.out)
ff4e0dfb
         for n in self.program:
5267dfda
             print(n, end=' ', file=self.out)
         print(file=self.out)
ff4e0dfb
 
     def set_pctr(self, v):
         self.program_ctr = v
     def begin_until(self, stack, rstack):
         self.program_ctr += 1 # since the program counter doesn't update until after the yield
         mark = self.program_ctr
         word = ' '
         loop_count = 0
         words = []
         while True:
             word = self.read_word()
             if word == 'begin': loop_count += 1
             elif word == 'until':
                 if loop_count == 0: break
                 else: loop_count -= 1
             words.append(word)
 
     def mark_r(self, stack, rstack):
         self.jmpr.append(self.program_ctr-1)
         #print('markr', self.jmpr)
     def jmp_r(self, stack, rstack):
         dst = self.jmpr.pop()
         self.set_pctr(dst)
         #print('jmpr', dst, self.jmpr)
     def jmp_clr(self, stack, rstack):
         self.jmpr.pop()
         #print('clrjmp', self.jmpr)
 
     def mark(self, stack, rstack):
         self.stack.append(self.program_ctr-1)
     def jmp(self, stack, rstack):
         dst = stack.pop()
         self.set_pctr(dst)
 
5267dfda
     def __init__(self, out=sys.stdout):
ff4e0dfb
         self.sym_words = SymbolDict()
         self.stack = []
         self.rstack = []
         self.program = []
         self.jmpr = []
         self.pcs = []
         self.mode = 0; # 0 - Normal; 1 - string reading
         self.program_ctr = 0
5267dfda
         self.out = out
ff4e0dfb
         self.add_symbol('words')(self.words)
         self.add_symbol('.p')(self.prog_print)
         self.add_symbol(':')(self.colon)
         self.add_symbol('forget')(self.forget)
         self.add_symbol('if')(self.if_)
         self.add_symbol('jmp')(self.jmp)
         self.add_symbol('rev')(self.rev_pctr)
         self.add_symbol('adv')(self.adv_pctr)
         self.add_symbol('adv0')(self.adv_pctr0)
         self.add_symbol('mark')(self.mark)
         self.add_symbol('markr')(self.mark_r)
         self.add_symbol('jmpr')(self.jmp_r)
         self.add_symbol('clrjmp')(self.jmp_clr)
 
     def __getword(self):
         while self.inp[-1] == []: self.inp.pop()
         if hasattr(self.inp[-1], 'read'):
             self.inp[-1] = read_word(self.inp[-1])
         if hasattr(self.inp[-1], 'next'):
             result = self.inp[-1].next()
         else:
             result = self.inp[-1].pop(0)
         self.program.append(result)
 
     def read_word(self):
         while True:
             while self.program_ctr >= len(self.program):
                 self.__getword()
             yield self.program[self.program_ctr]
             self.program_ctr += 1
 
     def read(self, inp, silent=False):
         self.inp = [inp]
         delim = SPACE
5267dfda
         if not silent: print(self.program_ctr, end='? ', file=self.out)
ff4e0dfb
         for word in self.read_word():
             #print('<%s>' % word, end=' ')
             if word == _RUN:
                 if not silent:
5267dfda
                     print(' ok', file=self.out)
                     print(self.program_ctr, end='? ', file=self.out)
ff4e0dfb
                 continue
             elif word == '."':
                 out = []
                 word = self.__getword()
                 while word != '"':
                     out.append(self.__getword)
                     word = self.__getword()
5267dfda
                 print(' '.join(out), file=self.out)
ff4e0dfb
                 continue
             elif word == '': continue
             self.stack.append(word)
             self.lookup()
     def lookup(self, sym=None):
         if sym is None:
             sym = self.stack.pop()
         result = None
         if sym.lower() in self.symbols:
5267dfda
             with redir_stdout(self.out):
                 result = self.symbols[sym.lower()](self.stack, self.rstack)
ff4e0dfb
         else:
             result = self.numberp(sym)
             if result is None: raise ValueError("Symbol %r not found" % sym)
             else:
                 self.stack.append(result)
         return result
5267dfda
 
ff4e0dfb
     def numberp(self, sym):
         is_valid = -1 # -1 = undecided, 0 = False, 1 = True
         typ = int
         dot_acceptable = False
         if sym == '': is_valid = 0
         elif not (sym.startswith('-') or sym[0].isdigit()): is_valid = 0
         if is_valid == 0: return None
         else:
             for char in sym[1:]:
                 if char.isdigit(): dot_acceptable = True
                 elif dot_acceptable and char == '.': typ = float
                 elif dot_acceptable and char == 'e': typ = float
                 else:
                     return None
             else:
                 return typ(sym)
     @classmethod
     def add_symbol(cls, name):
         def _i1(func):
             cls.symbols[name.lower()] = func
             return func
         return _i1
 
 @Reader.add_symbol('J')
 def J(stack, rstack):
     stack.append(rstack[-3])
 
 @Reader.add_symbol('R@')
 def rAt(stack, rstack):
     stack.append(rstack[-1])
 
 @Reader.add_symbol('I')
 def I(stack, rstack):
     stack.append(rstack[-1])
 
 @Reader.add_symbol('R>')
 def movP(stack, rstack):
     stack.append(rstack.pop())
 
 @Reader.add_symbol('>R')
 def movR(stack, rstack):
     rstack.append(stack.pop())
 
 @Reader.add_symbol('2over')
 def _2over(stack, rstack=None):
     d, c, b, a = [stack.pop() for _ in range(4)]
     stack.append(a)
     stack.append(b)
     stack.append(c)
     stack.append(d)
     stack.append(a)
     stack.append(b)
 
 @Reader.add_symbol('2dup')
 def _2dup(stack, rstack=None):
     b, a = [stack.pop() for _ in range(2)]
     stack.append(a)
     stack.append(b)
     stack.append(a)
     stack.append(b)
 
 @Reader.add_symbol('2swap')
 def _2swap(stack, rstack=None):
     d, c, b, a = [stack.pop() for _ in range(4)]
     stack.append(c)
     stack.append(d)
     stack.append(a)
     stack.append(b)
 
 @Reader.add_symbol('.r')
 def stack_print(stack, rstack=None):
     print('<%d>' % len(rstack), end=' ')
     for n in rstack:
         print(n, end=' ')
     print()
 
 @Reader.add_symbol('.s')
 def stack_print(stack, rstack=None):
     print('<%d>' % len(stack), end=' ')
     for n in stack:
         print(n, end=' ')
     print()
 
 @Reader.add_symbol('drop')
 def drop(stack, rstack=None):
     stack.pop()
 
 @Reader.add_symbol('rot')
 def rot(stack, rstack=None):
     n1, n2, n3 = stack.pop(), stack.pop(), stack.pop()
     #a,  b,  c
     stack.extend([n2, n1, n3])
 
 @Reader.add_symbol('dup')
 def dup(stack, rstack=None):
     a = stack.pop()
     stack.append(a)
     stack.append(a)
 
 @Reader.add_symbol('over')
 def over(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a)
     stack.append(b)
     stack.append(a)
 
 @Reader.add_symbol('swap')
 def swap(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(b)
     stack.append(a)
 
 @Reader.add_symbol('%')
 def mod(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a % b)
 
 @Reader.add_symbol('/')
 def div(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a / b)
 
 @Reader.add_symbol('*')
 def mul(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a * b)
 
 @Reader.add_symbol('-')
 def min_(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a - b)
 
 @Reader.add_symbol('+')
 def add(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a + b)
 
 @Reader.add_symbol('and')
 def and_(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a and b)
 
 @Reader.add_symbol('or')
 def or_(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(a or b)
 
 @Reader.add_symbol('.')
 def period(stack, rstack=None):
     print(stack.pop(), end=' ')
 
 @Reader.add_symbol('emit')
 def emit(stack, rstack=None):
     print(chr(stack.pop()), end='')
 
 @Reader.add_symbol('=')
 def eql(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(int(a == b))
 
 @Reader.add_symbol('<')
 def lt(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(int(a < b))
 
 @Reader.add_symbol('>')
 def gt(stack, rstack=None):
     b, a = stack.pop(), stack.pop()
     stack.append(int(a > b))
 
 @Reader.add_symbol('nop')
 def nop(*_):
     pass
 
 @Reader.add_symbol('clear')
 def clear(stack, rstack):
     del stack[:]
     del rstack[:]
 
5267dfda
 def init_reader():
ff4e0dfb
     reader = Reader()
     reader.compile(*'0= 0 ='.split(None,1))
     reader.compile(*'0> 0 >'.split(None,1))
     reader.compile(*'0< 0 <'.split(None,1))
     reader.compile('cr', ['10', 'emit'])
     reader.compile('2drop', ['drop', 'drop'])
     reader.compile('false', ['0'])
     reader.compile('true', ['-1'])
     reader.compile('invert', ['0=', 'if', 'true', 'else', 'false', 'then'])
     reader.compile('?dup', 'dup if dup then'.split())
     reader.compile('1+', '1 +'.split())
     reader.compile('1-', '1 -'.split())
     reader.compile('2+', '2 +'.split())
     reader.compile('2-', '2 -'.split())
     reader.compile('2*', '2 *'.split())
     reader.compile('2/', '2 /'.split())
     reader.compile('abs', 'dup 0< if -1 * then'.split())
     reader.compile('negate', '-1 *'.split())
     reader.compile('min', '2dup - 0> if swap then drop')
     reader.compile('max', '2dup - 0< if swap then drop')
     reader.compile('*/', 'rot rot * swap /')
     reader.compile('*/mod', 'rot rot * swap /mod')
     reader.compile('/mod1', '2dup % dup 2swap / rot drop')
     reader.compile('mark>r', 'mark >r')
     reader.compile('arrow', '45 emit 45 emit 62 emit')
     reader.compile('loop0', '0= arrow .s if  98 emit cr else 97 emit cr .s r> .s jmp then')
     reader.compile(*'spaces 0 do 32 emit loop'.split(None, 1))
     reader.compile(*'do swap >r >r begin'.split(None, 1))
     reader.compile('rdrop', 'r> drop')
     reader.compile(*'loop r> 1+ r> 2dup >r >r < invert until rdrop rdrop'.split(None, 1))
     reader.compile('begin', 'markr')
     reader.compile('until', 'invert if jmpr else clrjmp then')
5267dfda
     return reader
 
 if __name__ == '__main__':
     import sys
     reader = init_reader()
ff4e0dfb
 
     import argparse
     parser = argparse.ArgumentParser()
     parser.add_argument('file', nargs='?')
     args = parser.parse_args()
     if args.file == '-' or args.file == None:
         fil = sys.stdin
         reader.read(sys.stdin)
     else:
         with open(args.file) as f:
             reader.read(f, silent=True)