User:Marinus/LOCK interpreter

From Esolang
Jump to: navigation, search

This is a LOCK interpreter, implementing the specification as of 22 May 2010.

There are a few extra options for I/O for testing purposes.

-c ASCII (Brainfuck-style) I/O instead of numbers.
-r If the user enters something that is not a valid number, let the user retry instead of handling it as a failed I/O operation.
-f x Have failed input operations return the value x (must be an integer), the default is 0
-F Halt the program when I/O fails instead of returning a value
#!/usr/bin/env python

import sys, getopt

# lock interpreter

class ParseError(Exception): pass
class ProgramError(Exception): pass

class Block:
   def __init__(self, keys, code):
      self.keys = keys[:]
      self.code = code[:]
   # run the code in the block if possible
   # returns False if block can't run
   def run(self, env):
      cmds = { '>': env.addkey,
               '<': env.delkey,
               '?': env.condaddkey,
               '+': env.add,
               '-': env.sub,
               '*': env.mul,
               '/': env.div,
               '%': env.mod,
               '=': env.setvar,
               '^': env.setaccv,
               ':': env.readint,
               '$': env.outvar,
               '#': env.setaccn,
               '&': lambda foo: 0
      # don't run if not all keys are present
      if False in [key in env.keys for key in self.keys]:
         return False
      for line in self.code:
         if line[0] in cmds:
            # it's a command
            try: cmds[line[0]](line[1:])
            except ZeroDivisionError:
                raise ProgramError("division by zero");
            # it's a string to be output
      return True

class Environment:

   # intio = read in and write out numbers (false=ASCII)
   # retry = if intio, have user retry inputting if input not a number
   # onfail = returned as read if read fails (None=stop program)
   def __init__(self, blocks, intio=True, retry=False, onfail=0):
      self.blocks = blocks
      self.keys = set(('main',))
      self.vars = {}
      self.acc = 0
      self.input, self.output = self.iofuncs(intio, retry, onfail)
   def iofuncs(self, intio, retry, onfail):
      if intio:
         # functions for integer input/output
         def i():
            while True:
               l = sys.stdin.readline()
               if l=='': # eof
                  if onfail!=None: return onfail
                  else: raise ProgramError("Read failed: end of file")
                  a = int(l)
                  return a
               except ValueError:
                  if onfail!=None: return onfail
                  if retry:
                     print "%s is not a number, please input a number:"%s ,
                     raise ProgramError("Read failed: not a number")
         def o(val):
         # functions for ASCII input/output
         def i():
            ch =
            if ch == '':
               if onfail!=None: return onfail
               else: raise ProgramError("Read failed: end of file")
            return ord(ch)
         def o(val):
      return i, o 
   def gv(self,var): 
      if var in self.vars: return self.vars[var]
      else: return 0
   def sv(self,var,val):
      self.vars[var] = int(val)
   def addkey(self, key): self.keys.add(key)
   def delkey(self, key): 
       try: self.keys.remove(key)
       except KeyError: pass # removing a key that isn't there is not an error
   def condaddkey(self, key): self.acc and self.keys.add(key)
   def add(self, var): self.acc += self.gv(var)
   def sub(self, var): self.acc -= self.gv(var)
   def mul(self, var): self.acc *= self.gv(var)
   def div(self, var): self.acc /= self.gv(var)
   def mod(self, var): self.acc %= self.gv(var)
   def setvar(self, var):, self.acc)
   def setaccv(self, var): self.acc = self.gv(var)
   def readint(self, var):, self.input())
   def outvar(self, var): self.output(self.gv(var))
   def setaccn(self, num): 
         self.acc = int(num)
      except ValueError:
         raise ProgramError("Error: not a number: %s" % num)
   def outstr(self, str):
                          .replace(r'\ ',' '))
   def run(self):
      t = True
      while t:
         for block in self.blocks:
            t =
            if t: break

def parse(code):
   lines = [line.lstrip() for line in code.split("\n")]
   blocks = []
   curkeys = None
   curcode = None
   curline = 0
   inblock = False
   for line in lines:
      curline += 1
      if line == '': continue
      if line[0] == '{':
         # new block
         if inblock:
            raise ParseError("line %d: nested blocks are not allowed"%curline)
         if line[1:] == '':
            raise ParseError("line %d: missing keys after {"%curline)
         curkeys = [key.strip() for key in line[1:].split(',')]
         curcode = []
         inblock = True
      elif line[0] == '}':
         # end of block
         if not inblock:
            raise ParseError("line %d: unbalanced }"%curline)
         blocks.append(Block(curkeys, curcode))
         inblock = False
         if not inblock:
            raise ParseError("line %d: code not allowed outside of block"%curline)
   if inblock:
      # block wasn't closed
      raise ParseError("unbalanced {");
   return blocks

def usage():
   print """Usage: [-h] [-c | -r] [-f x | -F] file
     -h: print this help
     -c: use ASCII i/o (Brainfuck-style) instead of integer i/o 
     -r: in integer mode, let the user retry if something other
         than a number is input
     -f x: return x on read failure (EOF or, if -r is not given,
         when the user inputs something that can't be parsed into
         an integer.)
         Default = 0.
     -F: halt program on read failure instead of returning a default

def main(argv):
      opts, args = getopt.getopt(argv[1:], "hcrFf:")
   except getopt.GetoptError, e:
      print e
   onfail = 0
   retry = False
   intio = True
   for o, a in opts:
      if o == '-h':
      elif o == '-c':
         intio = False
      elif o == '-r':
         retry = True
      elif o == '-F':
         onfail = None
      elif o == '-f':
            onfail = int(a)
         except ValueError:
            print "error: argument to -f must be a number."

   if len(args) != 1:
      print "argument error."
      Environment(parse(file(args[0]).read()), intio, retry, onfail).run()
   except ParseError, e:
      print "parse error:", e
   except ProgramError, e:
      print "runtime error:", e
   except IOError, e:
      print "can't read file:", e
if __name__=='__main__': main(sys.argv)