User:Marinus/LOCK interpreter

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


 * 1) !/usr/bin/env python

import sys, getopt


 * 1) lock.py
 * 2) 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"); else: # it's a string to be output env.outstr(line) 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") try: 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 , else: raise ProgramError("Read failed: not a number") def o(val): sys.stdout.write(str(val)) else: # functions for ASCII input/output def i: ch = sys.stdin.read(1) if ch == '': if onfail!=None: return onfail else: raise ProgramError("Read failed: end of file") return ord(ch) def o(val): sys.stdout.write(chr(val%256)) 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.sv(var, self.acc) def setaccv(self, var): self.acc = self.gv(var) def readint(self, var): self.sv(var, self.input) def outvar(self, var): self.output(self.gv(var)) def setaccn(self, num): try: self.acc = int(num) except ValueError: raise ProgramError("Error: not a number: %s" % num) def outstr(self, str): sys.stdout.write(str.replace(r'\n',"\n")                         .replace(r'\t',"\t")                          .replace(r'\\',"\\")                          .replace(r'\ ',' ')) def run(self): t = True while t:        for block in self.blocks: t = block.run(self) 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 else: if not inblock: raise ParseError("line %d: code not allowed outside of block"%curline) curcode.append(line) if inblock: # block wasn't closed raise ParseError("unbalanced {"); return blocks

def usage: print """Usage: lock.py [-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         value """

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

if len(args) != 1: print "argument error." usage sys.exit try: 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)