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. |
0 – 9
|
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()