mirror of https://github.com/dirtbags/moth.git
291 lines
7.4 KiB
Python
Executable File
291 lines
7.4 KiB
Python
Executable File
#! /usr/bin/python
|
|
|
|
"""A shitty FORTH interpreter
|
|
|
|
15:58 <SpaceHobo> WELCOME TO FORF!
|
|
15:58 <SpaceHobo> *PUNCH*
|
|
"""
|
|
|
|
import operator
|
|
|
|
class ParseError(Exception):
|
|
pass
|
|
|
|
class Overflow(Exception):
|
|
pass
|
|
|
|
class Underflow(Exception):
|
|
pass
|
|
|
|
class Stack:
|
|
def __init__(self, init=None, size=50):
|
|
self.size = size
|
|
self.stack = init or []
|
|
|
|
def __str__(self):
|
|
if not self.stack:
|
|
return '{}'
|
|
guts = ' '.join(repr(i) for i in self.stack)
|
|
return '{ %s }' % guts
|
|
__repr__ = __str__
|
|
|
|
def push(self, *values):
|
|
for val in values:
|
|
if len(self.stack) == self.size:
|
|
raise Overflow()
|
|
self.stack.append(val)
|
|
|
|
def extend(self, other):
|
|
self.stack.extend(other.stack)
|
|
|
|
def dup(self):
|
|
return Stack(init=self.stack[:], size=self.size)
|
|
|
|
def pop(self):
|
|
if not self.stack:
|
|
raise Underflow()
|
|
return self.stack.pop()
|
|
|
|
def mpop(self, n):
|
|
return [self.pop() for i in range(n)]
|
|
|
|
def __nonzero__(self):
|
|
return bool(self.stack)
|
|
|
|
|
|
class Environment:
|
|
def __init__(self, ticks=2000, codelen=500):
|
|
self.ticks = ticks
|
|
self.codelen = codelen
|
|
self.registers = [0] * 10
|
|
self.unfuncs = {'~' : operator.inv,
|
|
'!' : operator.not_,
|
|
'abs': operator.abs,
|
|
}
|
|
self.binfuncs = {'+' : operator.add,
|
|
'-' : operator.sub,
|
|
'*' : operator.mul,
|
|
'/' : operator.div,
|
|
'%' : operator.mod,
|
|
'**': operator.pow,
|
|
'&' : operator.and_,
|
|
'|' : operator.or_,
|
|
'^' : operator.xor,
|
|
'<<': operator.lshift,
|
|
'>>': operator.rshift,
|
|
'>' : operator.gt,
|
|
'>=': operator.ge,
|
|
'<' : operator.lt,
|
|
'<=': operator.le,
|
|
'=' : operator.eq,
|
|
'<>': operator.ne,
|
|
'!=': operator.ne,
|
|
}
|
|
self.data = Stack()
|
|
|
|
def get(self, s):
|
|
unfunc = self.unfuncs.get(s)
|
|
if unfunc:
|
|
return self.apply_unfunc(unfunc)
|
|
|
|
binfunc = self.binfuncs.get(s)
|
|
if binfunc:
|
|
return self.apply_binfunc(binfunc)
|
|
|
|
try:
|
|
return getattr(self, 'cmd_' + s)
|
|
except AttributeError:
|
|
return None
|
|
|
|
def apply_unfunc(self, func):
|
|
"""Apply a unary function"""
|
|
|
|
def f(data):
|
|
a = data.pop()
|
|
data.push(int(func(a)))
|
|
return f
|
|
|
|
def apply_binfunc(self, func):
|
|
"""Apply a binary function"""
|
|
|
|
def f(data):
|
|
a = data.pop()
|
|
b = data.pop()
|
|
data.push(int(func(b, a)))
|
|
return f
|
|
|
|
def run(self, s):
|
|
self.parse_str(s)
|
|
self.eval()
|
|
|
|
def parse_str(self, s):
|
|
tokens = s.strip().split()
|
|
tokens.reverse() # so .parse can tokens.pop()
|
|
self.code = self.parse(tokens)
|
|
|
|
def parse(self, tokens, token=0, depth=0):
|
|
if depth > 4:
|
|
raise ParseError('Maximum recursion depth exceeded at token %d' % token)
|
|
code = []
|
|
while tokens:
|
|
val = tokens.pop()
|
|
token += 1
|
|
f = self.get(val)
|
|
if f:
|
|
code.append(f)
|
|
elif val == '(':
|
|
# Comment
|
|
while val != ')':
|
|
val = tokens.pop()
|
|
token += 1
|
|
elif val == '{}':
|
|
# Empty block
|
|
code.append(Stack())
|
|
elif val == '{':
|
|
block = self.parse(tokens, token, depth+1)
|
|
code.append(block)
|
|
elif val == '}':
|
|
break
|
|
else:
|
|
# The only literals we support are ints
|
|
try:
|
|
code.append(int(val))
|
|
except ValueError:
|
|
raise ParseError('Invalid literal at token %d (%s)' % (token, val))
|
|
if len(code) > self.codelen:
|
|
raise ParseError('Code stack overflow')
|
|
# Reverse so we can .pop()
|
|
code.reverse()
|
|
return Stack(code, size=self.codelen)
|
|
|
|
def eval(self):
|
|
ticks = self.ticks
|
|
code_orig = self.code.dup()
|
|
while self.code and ticks:
|
|
ticks -= 1
|
|
val = self.code.pop()
|
|
try:
|
|
if callable(val):
|
|
val(self.data)
|
|
else:
|
|
self.data.push(val)
|
|
except Underflow:
|
|
self.err('Stack underflow at proc %r' % (val))
|
|
except Overflow:
|
|
self.err('Stack overflow at proc %r' % (val))
|
|
if self.code:
|
|
self.err('Ran out of ticks!')
|
|
self.code = code_orig
|
|
|
|
def err(self, msg):
|
|
print 'Error: %s' % msg
|
|
|
|
def msg(self, msg):
|
|
print msg
|
|
|
|
##
|
|
## Commands
|
|
##
|
|
def cmd_print(self, data):
|
|
a = data.pop()
|
|
self.msg(a)
|
|
|
|
def cmd_dumpstack(self, data):
|
|
a = data.pop()
|
|
self.msg('(dumpstack %d) %r' % (a, data.stack))
|
|
|
|
def cmd_dumpmem(self, data):
|
|
a = data.pop()
|
|
self.msg('(dumpmem %d) %r' % (a, self.registers))
|
|
|
|
def cmd_exch(self, data):
|
|
a, b = data.mpop(2)
|
|
data.push(a, b)
|
|
|
|
def cmd_dup(self, data):
|
|
a = data.pop()
|
|
data.push(a, a)
|
|
|
|
def cmd_pop(self, data):
|
|
data.pop()
|
|
|
|
def cmd_store(self, data):
|
|
a, b = data.mpop(2)
|
|
self.registers[a % 10] = b
|
|
|
|
def cmd_fetch(self, data):
|
|
a = data.pop()
|
|
data.push(self.registers[a % 10])
|
|
|
|
##
|
|
## Evaluation commands
|
|
##
|
|
def eval_block(self, block):
|
|
try:
|
|
self.code.extend(block)
|
|
except TypeError:
|
|
# If it's not a block, just append it
|
|
self.code.push(block)
|
|
|
|
def cmd_if(self, data):
|
|
block = data.pop()
|
|
cond = data.pop()
|
|
if cond:
|
|
self.eval_block(block)
|
|
|
|
def cmd_ifelse(self, data):
|
|
elseblock = data.pop()
|
|
ifblock = data.pop()
|
|
cond = data.pop()
|
|
if cond:
|
|
self.eval_block(ifblock)
|
|
else:
|
|
self.eval_block(elseblock)
|
|
|
|
def cmd_eval(self, data):
|
|
# Interestingly, this is the same as "1 exch if"
|
|
block = data.pop()
|
|
self.eval_block(block)
|
|
|
|
def cmd_call(self, data):
|
|
# Shortcut for "fetch eval"
|
|
self.cmd_fetch(data)
|
|
self.cmd_eval(data)
|
|
|
|
|
|
def repl():
|
|
env = Environment()
|
|
while True:
|
|
try:
|
|
s = raw_input('>8[= =] ')
|
|
except (KeyboardInterrupt, EOFError):
|
|
print
|
|
break
|
|
try:
|
|
env.run(s)
|
|
print env.data
|
|
except ParseError, err:
|
|
print r' \ nom nom nom, %s!' % err
|
|
print r' \ bye bye!'
|
|
|
|
if __name__ == '__main__':
|
|
import sys
|
|
import time
|
|
try:
|
|
import readline
|
|
except ImportError:
|
|
pass
|
|
|
|
if len(sys.argv) > 1:
|
|
s = open(sys.argv[1]).read()
|
|
env = Environment()
|
|
begin = time.time()
|
|
env.run(s)
|
|
end = time.time()
|
|
elapsed = end - begin
|
|
print 'Evaluated in %.2f seconds' % elapsed
|
|
else:
|
|
print 'WELCOME TO FORF!'
|
|
print '*PUNCH*'
|
|
repl()
|