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)