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)