Talk:Rain

From Esolang
Jump to navigation Jump to search

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.

-- Dok (talk) 12:39, 7 March 2017 (UTC)

Okay, I added that extra tunneling behavior. Do you intend to write a compiler for this? It does seem like it would be possible, though quite difficult still. --Quintopia (talk) 04:19, 9 March 2017 (UTC)