User:Marinus/LOCK interpreter
Jump to navigation
Jump to 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.py # 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)