User:Marinus/FeckFeck Interpreter

From Esolang
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)