Talk:Rain
A Python Implementation
There were a lot of things in this spec left unsaid, but I did my best to infer and extend the meanings of the things that were said. Could you please confirm that the following interpreter is "standards-compliant" (i.e. works the same as your reference interpreter). I tested it on the example programs and those seem to behave as expected for the most part.
import sys import functools class Rain: class IdentityDict(dict): def __init__(self,rain): self.rain = rain super(rain.IdentityDict,self).__init__() def __missing__(self,key): def default(stack): if rain.stringmode: return stack+[ord(key)] else: print "%c is not a valid command at (%d,%d)"%(rain.prog[rain.row][rain.col],rain.row,rain.col) sys.exit(1) return default def errors(self,func): def wrapped(stack): try: return func(stack) except IndexError: print "Tried to pop an empty stack at (%d,%d)"%(self.col,self.row) sys.exit(1) except ZeroDivisionError: print "Tried to divide by zero at (%d,%d)"%(self.col,self.row) sys.exit(1) return wrapped def stringwrapper(self,c,func): def wrapped(stack): if self.stringmode: return stack+[ord(c)] else: return func(stack) return wrapped def seek(self,dir): def wrapped(stack): try: self.callstack.append((self.row,self.col,self.dir)) self.variables.append(0) tunnelinglevel = 0 while not self.prog[self.row][self.col]=='|' or tunnelinglevel: self.col+=dir if self.prog[self.row][self.col]=='?]['[dir]: tunnelinglevel += 1 elif self.prog[self.row][self.col]=='?[]'[dir]: if tunnelinglevel > 0: tunnelinglevel -= 1 self.dir*=-1 return stack except IndexError: print "Tried to jump out of bounds at (%d,%d)"%(self.col,self.row) sys.exit(1) return wrapped def jump(self,stack): self.row+=self.dir return stack def conditionally(self,func): def wrapped(stack): if stack.pop(): return func(stack) else: return stack return wrapped def store(self,stack): self.variables[-1]=stack.pop() return stack def output(self,type): def wrapped(stack): c = stack.pop() if type: sys.stdout.write(chr(c)) else: sys.stdout.write(str(c)) return stack return wrapped def toggle(self,delimiter): def wrapped(stack): if self.stringmode==delimiter: self.stringmode = False elif not self.stringmode: self.stringmode = delimiter else: return stack+ord("?'\""[delimiter]) return stack return wrapped def inputs(self,type): def wrapped(stack): if type: if len(self.inputbuffer)==0: try: self.inputbuffer = map(ord,raw_input())+[10] #add the stripped newline back in except KeyboardInterrupt: sys.exit(0) except EOFError: return stack+[-1] return stack + [self.inputbuffer.pop(0)] else: try: return stack + [input()] except (NameError,SyntaxError,EOFError): return stack+[-1] return wrapped def comeback(self,stack): if len(self.callstack): (self.row,self.col,self.dir)=self.callstack.pop() self.variables.pop() return stack else: sys.exit(0) def __init__(self,prog): self.prog = prog self.variables = [0] self.stack = [] self.callstack = [] self.row=0 self.col=0 self.dir=1 self.stringmode=False self.inputbuffer=[] self.commands = self.IdentityDict(self) for c in range(10): self.commands[chr(c+48)]=functools.partial(lambda k,x:x + [k],c) self.commands[':']=self.commands['|']=self.commands['[']=self.commands[']']=lambda x: x self.commands['+']=self.errors(lambda x: x[:-2]+[x[-2]+x[-1]]) self.commands['-']=self.errors(lambda x: x[:-2]+[x[-2]-x[-1]]) self.commands['*']=self.errors(lambda x: x[:-2]+[x[-2]*x[-1]]) self.commands['/']=self.errors(lambda x: x[:-2]+[x[-2]/x[-1]]) self.commands['%']=self.errors(lambda x: x[:-2]+[x[-2]%x[-1]]) self.commands[')']=self.errors(lambda x: x[:-2]+[int(x[-2]>x[-1])]) self.commands['(']=self.errors(lambda x: x[:-2]+[int(x[-2]<x[-1])]) self.commands['=']=self.errors(lambda x: x[:-2]+[int(x[-2]==x[-1])]) self.commands['>']=self.seek(1) self.commands['<']=self.seek(-1) self.commands['!']=self.jump self.commands['?']=self.conditionally(self.jump) self.commands['^']=self.errors(lambda x:x+[x[-1]]) self.commands['\\']=self.errors(lambda x:x[:-2]+[x[-1]]+[x[-2]]) self.commands['_']=self.errors(lambda x:x+[x[-2]]) self.commands['~']=lambda x:x+[len(x)] self.commands['`']=self.errors(lambda x:x[:-1]) self.commands['.']=self.errors(self.store) self.commands[',']=lambda x:x+[self.variables[-1]] self.commands['#']=self.errors(self.output(0)) self.commands['$']=self.errors(self.output(1)) self.commands['"']=self.toggle(2) self.commands["'"]=self.toggle(1) self.commands['&']=self.inputs(0) self.commands['@']=self.inputs(1) self.commands[';']=self.comeback for c,f in self.commands.items(): if c not in '"\'': self.commands[c]=self.stringwrapper(c,f) def run(self): while True: try: self.stack = self.commands[self.prog[self.row][self.col]](self.stack) #print "(%d,%d,%c)"%(self.row,self.col,self.prog[self.row][self.col]) #print self.stack except IndexError: print "Try to execute out of bounds at (%d,%d)"%(self.col,self.row) sys.exit(1) self.row+=self.dir if __name__=="__main__": if len(sys.argv)<2: print "Please provide a program filename to execute." sys.exit(1) try: with open(sys.argv[1],'r') as f: prog = [line for line in f] except IOError: print "File not found or unreadable: %s"%(sys.argv[1]) sys.exit(1) rain = Rain(prog) rain.run()
In particular, I don't know if I'm handling input correctly, but (read -1 on EOF or invalid integer input) seems to work best with the examples provided. Also I don't know if I'm handling tunneling correctly because no examples were provided for how it should work. I assume that >]>]|[|[| should skip all the way from the first column to the last. I also assumed that these tunneling commands would be invalid if encountered outside of jumping between columns (as this would be a good reason to include a blank column between columns as you've done), but I could see the case for their being nops as well. Lastly, I am assuming that every time you switch columns using > and <, you add a new local variable for that column, preserving unaltered any local variables that were used for that same column higher up the call chain. Are all these assumptions as you had desired?
--Quintopia (talk) 08:32, 1 March 2017 (UTC)
- This interpreter seem to work as exepected, your assumptions for tunneling and local variables when switching columns are right. As for tunneling you can only have a positive tuneling depth :
> ] > ] | [ | [ |
will skip from the first column to the last, however| ] > [ |
is also valide and it will jump from the middle column to the last. If a tunneling commands is encountered outside of jumping between column the result is not specified (at best act as a nop, else give an error).
- Every time you switch columns using > and <, you add a new local variable for that column, preserving unaltered any local variables that were used for that same column higher up the call chain. Yes it's True.
- For input -1 is produced for EOF, but this might change in the future, i still dont really know how to deal with EOF.