User:Marinus/FeckFeck Interpreter
Jump to navigation
Jump to search
This is an interpreter for FeckFeck. I came across this language recently and thought it needed an entry and an interpreter, so here it is. I hope it's not too offensive, though if Brainfuck is allowed then this shouldn't give many problems.
#!/usr/bin/env python
# FeckFeck interpreter
# by Marinus Oosters
# Reading stdin on EOF returns -1. Well, -1 % 256... This wasn't in the spec
# but it's what almost all programming languages (read: C and friends) do on
# EOF if you don't specifically code something in.
# Usage: feckfeck.py <program>
# or: feckfeck.py -b <program> to convert to Brainfuck.
# I hope you guys don't mind profanity :)
import sys
# convert to brainfuck
def brainfuck(cmds):
return "".join(["><+-.,[]"[c.cmd]*(c.repeat+1) for c in cmds])
# parser
class Parser:
class ParseError(Exception): pass;
class Command:
FUCK,SHAG,BOOB,TITS,COCK,KNOB,ARSE,BUTT = range(8)
def __init__(self, cmd, repeat):
self.cmd, self.repeat = cmd, repeat
# damn it, parsing this is not fun
@staticmethod
def parse(string):
curcmd = ""
cmds = []
i = 0
while i < len(string):
# check for comments
if string[i:i+2] == '/*':
commentstart = i
# skip comment
i += 1
while i < len(string):
i += 1
if string[i:i+2] == '*/': break
i += 2
if i >= len(string):
raise Parser.ParseError("unterminated comment starting at index %i"%commentstart)
# find command
if string[i] in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*":
curcmd += string[i]
if len(curcmd)==4:
# this should be one
cmd = curcmd[0].lower() + curcmd[3].lower()
if not cmd in ('fk', 'sg', 'bb', 'ts', 'ck', 'kb', 'ae', 'bt'):
raise Parser.ParseError("invalid command %s at %i"%(curcmd,i))
else:
# find any exclamation marks between here and what would be the next otherwise valid character
repeat = 0
i+=1
while i < len(string):
if string[i] in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz*/":
break
if string[i] == '!': repeat += 1
i+=1
i -= 1
# repeating arses or butts is not allowed
if cmd in ('ae', 'bt') and repeat:
raise Parser.ParseError("repeating %s (by %i) at %i" % ('arse' if cmd=='ae' else 'butt', repeat, i))
c=Parser.Command
cmds.append( c({'fk': c.FUCK,
'sg': c.SHAG,
'bb': c.BOOB,
'ts': c.TITS,
'ck': c.COCK,
'kb': c.KNOB,
'ae': c.ARSE,
'bt': c.BUTT} [cmd], repeat) );
curcmd = ''
i += 1
return cmds
class Interpreter:
class RuntimeError(Exception): pass
def __init__(self, code):
self.mem = [0] * 1048576
self.ip = 0
self.mp = 0
self.code = code
c = Parser.Command
self.cmds = {c.FUCK: self.memop(self.pinc),
c.SHAG: self.memop(self.pdec),
c.BOOB: self.memop(self.inc),
c.TITS: self.memop(self.dec),
c.COCK: self.out,
c.KNOB: self.inp,
c.ARSE: self.jumpfwd,
c.BUTT: self.jumpback}
# memory operation wrapper to catch errors
def memop(self, f):
def fn(repeat):
f(repeat)
self.mem[self.mp] %= 256
if self.mp < 0: raise Interpreter.RuntimeError("memory pointer past zero at command %i" % self.ip)
if self.mp > len(self.mem): raise Interpreter.RuntimeError("out of memory at command %i" % self.ip)
return fn
# commands
def pinc(self, repeat): self.mp += repeat + 1
def pdec(self, repeat): self.mp -= repeat + 1
def inc(self, repeat): self.mem[self.mp] += repeat + 1
def dec(self, repeat): self.mem[self.mp] -= repeat + 1
def out(self, repeat): sys.stdout.write(chr(self.mem[self.mp]) * (repeat + 1))
def inp(self, repeat):
# when repeating, throwing away all characters but the last one read - this seems to be what is meant...
for i in range(repeat+1):
ch = sys.stdin.read(1)
# return -1 on EOF.
self.mem[self.mp] = ord(ch) if ch else (-1 % 256)
def jumpfwd(self, repeat):
if repeat: raise Interpreter.RuntimeError("repeated loop begin at command %i" % self.ip)
if self.mem[self.mp]: return
start = self.ip
c = Parser.Command
depth = 1
while depth > 0 and self.ip < len(self.code):
self.ip += 1
if self.code[self.ip].cmd == c.ARSE: depth += 1
if self.code[self.ip].cmd == c.BUTT: depth -= 1
if self.ip >= len(self.code): raise Interpreter.RuntimeError("unmatched arse at command %i" % start)
def jumpback(self, repeat):
if repeat: raise Interpreter.RuntimeError("repeated loop end at command %i" % self.ip)
if not self.mem[self.mp]: return
start = self.ip
c = Parser.Command
depth = 1
while depth > 0 and self.ip >= 0:
self.ip -= 1
if self.code[self.ip].cmd == c.ARSE: depth -= 1
if self.code[self.ip].cmd == c.BUTT: depth += 1
if self.ip < 0: raise Interpreter.RuntimeError("unmatched butt at command %i" % start)
# run program
def run(self):
self.ip = 0
self.mp = 0
while self.ip < len(self.code):
if not self.code[self.ip].cmd in self.cmds:
raise Interpreter.RuntimeError("unrecognized command %i" % self.ip)
self.cmds[self.code[self.ip].cmd](self.code[self.ip].repeat)
self.ip += 1
def usage():
print "Usage: feckfeck.py [-b] <file>"
print " if -b flag is given, program is converted to brainfuck, "
print " if not, the program is executed"
print
sys.exit(1)
def main(argv):
if len(argv) not in (2,3):
usage()
else:
if len(argv) == 2:
filename = argv[1]
bf = False
else:
if argv[1] != "-b":
usage()
else:
filename = argv[2]
bf = True
try:
if bf:
print brainfuck(Parser.parse(file(filename).read()))
else:
Interpreter(Parser.parse(file(filename).read())).run()
except Interpreter.RuntimeError, e:
print "Runtime error: %s." % e
sys.exit(2)
except Parser.ParseError, e:
print "Parse error: %s." % e
sys.exit(3)
except IOError, e:
print "Cannot read file %s: %s." % (filename, e)
sys.exit(4)
if __name__=='__main__': main(sys.argv)