Rain

From Esolang
Jump to navigation Jump to search
Rain
Paradigm(s) imperative
Designed by dok
Appeared in 2017
Memory system stack-based
Dimensions two-dimensional
Computational class
Reference implementation
Influenced by Befunge,><>
File extension(s) .rf

Rain is a two-dimensional fungeoidal esoteric programming language invented in 2017 by dok with the goal of being difficult to write while remaining possible to compile.

Concepts

Rain is a two-dimensional language, the code can be read in only two directions either up or down, left and right direction are used for function call. It is a stack-based language, so all operations are performed on a stack. In addition to the arithmetic stack a function call stack can be used to store information as one value can be stored and retrieved per function context. In a way each column can be considered as a method, thus libraries can be created and used with the necessity of doing the proper "placing and routing".

Code execution

Rain can be executed in two directions, from top to bottom or from bottom to top. The main program start out at (0,0), i.e. the top-left of the file. It initially moves down. Execution will not stop until a ; instruction is met (on the main program), or an error occurs. Trying to execute a space instruction will raise an error, trying to execute an instruction outside of the program bounds will raise an error too.

In the following example, the Instruction Pointer moves downwards :, make a jump to the right >, upwards | and return ;to the main program and stops ;.

: ;
> |
;  

Instructions

Rain has the following commands:

Cmd Description
: NOP: do nothing.
09 Push corresponding number onto the stack
+ Addition: Pop two values a and b, then push the result of a + b
- Subtraction: Pop two values a and b, then push the result of b - a
* Multiplication: Pop two values a and b, then push the result of a * b
/ Integer division: Pop two values a and b, then push the result of b / a, rounded down.
% Modulo: Pop two values a and b, then push the remainder of the integer division of b/a.
) Greater than: Pop two values a and b, then push 1 if b > a, otherwise 0.
( Lesser than: Pop two values a and b, then push 1 if b < a, otherwise 0.
= Equals: Pop two values a and b, then push 1 if y = x, otherwise 0.
| Intercept: only for left and right jump, else act as a NOP.
> Jump right: from the current position to the right, find the first intercept symbol, invert the direction and continue on the next instruction after the found intercept.
< Jump left: from the current position to the left, find the first intercept symbol, invert the direction and continue on the next instruction after the found intercept.
] and [ Tunnel: A jump outside of a tunnel will pass through the tunnel ignoring all intercept in it.
! Trampoline: jump over the following instruction in the current direction.
? Conditional trampoline: pop one value off the stack. The next instruction is only executed if the popped value is 0.
^ Duplicate top stack value
\ Swap top stack values
_ Over: push on the stack the value under the top element of the stack.
~ Push on the stack the length of the stack.
` Pop (remove) top stack value and discard
. Store: Pop one value off the stack to the local variable.
, Fetch: Push the value of the local variable.
# Pop top of stack and output as integer
$ Pop top of stack and output as ASCII character
" or ' Toggle stringmode (push each character's ASCII value all the way up respectively to the next " or ')
& Get integer from user and push it
@ Get character from user and push it
; End function call, return to cell next to last call (restore previous direction)

Examples

Hello, world!

:    
: ;  
: > |
: " $
: H $
: e $
: l $
: l $
: o $
: , $
:   $
: W $
: o $
: r $
: l $
: d $
: ! $
: " $
: + ;
: 5  
: 5  
: 0  
> |  
;

Cat program

: ;
| <
@ $
^ ;
0 ?
) )
? 0
; ^
$ @
> |
;

Factorial

Asks for a number n, and outputs it's factorial. Assumes the input is a non-negative integer.

& ;
^ > |
? 1 )
! ^ ?
> | ;
# : ^
5 : 1
5 : -
+ | <
$   *
;   ;


Fibonacci sequence

Asks for a number n, and outputs all the Fibonacci numbers up to fib(n), with n starting at 0 with fib(0) = 0 and fib(1) = 1.

0 ;   ;  
1 > | <  
& : . ,  
1 : : +  
+ : , _  
> | 0 > |
5   ) \ ^
5   ? . #
+   ; - "
$   , 1  
;   > | "
    ;   $
        ;

Ackermann function

Ask for two numbers an return the result of the Ackermann function.

& ;   ;
& > | < ;
> | _ : +
# : . : 1
5 : , : `
5 : ? : \
+ : > : |
$ : , :
; : ? :
  : ; :
  1 ^ :
  - . :
  1 , :
  ` ? :
  | < :
    , :
    ? :
    ; :
    _ :
    \ :
    1 :
    - :
    > |
    \ :
    1 :
    - :
    \ :
    > |
     ;


Pascal's Triangle

Ask for a number n and print the Pascal's Triangle up to n row

1       
1       ;
& ; ;   > |
| < ,   ; .
^ : > | ? ,
0 : + . ~ #
) : 5 > | 8
? : 5 , : 4
! , . $ : *
> 1 | ; : $
1 > | < | <
- . _ |   ,
^ : + :   ;
0 : . :
) : ~ :
^ : 1 :
? : = :
; : ? :
? : > |
! : ,  
> | ;  
;

print string

This method can be used to print the stack as characters up to the first null value.

         ;
call-> | <
       ^ :
       ? $
       ; :
       > |
       ;  

stack reverse

This method can be used to reverse the entire the stack.

         ; ;
call-> | < ,
       . | <
       ~ : ?
       1 : =
       = : 1
       ? : ~
       > | .
       , : \
       > > |
       ; ;

which is the same as :

public void reverse(Stack st) {
    int m = (int)st.Pop();
    if (st.Count != 1)
        reverse(st);
     Push(st , m);
}
public void Push(Stack st , int a) {
    int m = (int)st.Pop();
    if (st.Count != 0)
        Push(st , a);
     else
        st.Push(a);
     st.Push(m);
}

Implementations

In Python3 by User:Quintopia

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]:
                        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 = list(map(ord,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 + [eval(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['|']=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 list(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()