RASEL
Paradigm(s) | imperative |
---|---|
Designed by | User:Nakilon |
Appeared in | 2020 |
Type system | rational |
Memory system | stack-based |
Dimensions | two-dimensional |
Computational class | Turing complete |
Reference implementation | Github |
Influenced | Befunge, Funge-98 |
File extension(s) | .rasel , .rasela |
RASEL (Random Access Stack Esoteric Language) is a fungeoid esoteric programming language. The main goals of making another fungeoid were:
- to simplify implementation (compared to funge-98)
- to simplify specification (compared to both funge-98 and funge-93)
- to clarify specification (compared to both)
- to reduce the instruction set (18+digits compared to 26+digits in 93 and 52+digits in 98)
RASEL code is laid out on a two-dimensional, rectangular (and effectively toroidal) program space of instructions, each represented by a single ASCII character, lines separated by newlines. Unlike Befunge, RASEL is not self-modifying, that is, the instructions cannot change after the program is loaded. The additional ability of random access to the stack is added instead via the "swapn" -- modified `\` instruction that is now popping a depth parameter first. Stack data type is Rational while numerators and denominators are bignums. That means precision is limited only by the available memory of the host system, and there should be no rounding errors. Program space size also does not have any predefined limits and is calculated during the initial source code load.
See the GitHub repo for more detailed specification and more examples (in the ./examples
directory).
See this Esolang wiki page for more talk about the language and the rasel-ide
usage.
Instructions
@
|
exit with code popped from the stack |
0..9 A..Z
|
push single Base36 digit value onto the stack |
"
|
toggle "stringmode" (by default is off) |
:
|
"duplicate" -- pop a value and add it back to the stack twice |
> < ^ v
|
set instruction pointer direction |
#
|
"trampoline" -- the character under the next instruction pointer position will be ignored |
j
|
"jump forward" -- pop a value from the stack and jump over that many cells in the current instruction pointer direction |
- / %
|
pop two values and push the result of an arithmetic operation |
\
|
"swapn" -- pop a value N and swap the top with N+1th |
. ,
|
pop a value and print it as a number or a char of the corresponding ASCII code |
~
|
read character from STDIN, put its ASCII code onto the stack and work as "trampoline" unless EOF |
&
|
read Base10 non-negative integer from STDIN, put it onto the stack and work as "trampoline" unless EOF |
?
|
"if positive" -- pop a value and work as "trampoline" if it's positive |
Examples
Hello, world!
A"!dlrow ,olleH">:?@,Hj
Cat program
~@,
Quine (11 chars)
<@,Yj5#?:,"
Prime numbers generator
This implementation aggregates the prime numbers list in stack for speed, otherwise it can be shorter.
2:4v >- 2-\:--:.>01--# >::\:?^:3\1\%?v2-\1\:2\ 01-- >2-\:-- v
Other tricks
Converting to a one-liner
TODO
How do we check if the value is exactly 0 if we have only the instruction that checks if it is positive?
The naive approach would be to check if it is not positive and then additionally negate the value and check again. Here we make a list of values -2, -1, 0, +1, +2 and then check them:
2-01-012 5> :?@1-1\ :?v:-- v >01\-?vv ^ ,,,,,"true"A < ^ ,,,,,,"false"A <
$ rasel examples/naive_if_zero.rasel false false true false false
But we can apply the idea that if you multiply the negative value by itself it will become positive or just remain 0. Of course we don't have the "multiply" instruction but the "divide" effectively works the same for us (and it does not raise an error when we divide by 0):
2-01-012 5> :?@1-1\ :/?vv ^ ,,,,,"true"A < ^ ,,,,,,"false"A <
The check is now 3 times shorter.
IDE bundled with the reference implementation
When you review your fungeoid code after some time, like months or maybe even seconds, you may find yourself lost. Even printing the stack on every cycle might be not enough to figure things out because there are no variable identifiers neither in the debug output nor in the code. This is where the "annotated code" concept comes to the rescue. The IDE (rasel-ide
executable) allows you to add arbitrary annotation text lines to instructions (and even empty program space cells though it's less useful). Unlike the plain RASEL code such annotated format is stored as a JSON file with an extension .rasela
and looks like this:
$ head examples/prime.rasela [ [0,0,"2",null], [0,1,":","to be divided"], [0,2,"4",null], [0,3,"v",null], [0,9,">",null], [0,10,"$",null], [0,18,"2","yes"], [0,19,"-",null], [0,20,"\\","p"], ...
Conversion in both ways (of course losing the annotations when converting to the plain one) is possible with another bundled utility -- rasel-convert
. To start using the IDE you may either convert some existing .rasel
file to .rasela
and open it via rasel-ide my_program.rasela
or pass a path to the file that does not yet exist and IDE will create an empty (@
) file for you to start with.
The IDE runs as a local HTTP server that allows to edit the file at the path you've specified when you've launched the server and to run the code. Add or remove rows and columns by clicking the "+" and "X" buttons. Select the instruction cell by clicking on it. To edit the selected cell instruction just press any typing key on your keyboard. To clear the cell press Space (currently it also scrolls the page, I know). To annotate the cell click in the †ext input field above and type, then press Enter. Annotated cells are grey.
Here you can see an example of somewhat annotated code. Those annotations work not only as comments that you can read by clicking the annotated cell in the IDE. Imagine you've converted the examples/prime.rasel
to prime.rasela
and then to run the code in this format you are supposed to invoke the rasel-annotated
executable -- it prints things differently and also prints the stack on each execution step.
... ["loop",[11,17,13,11,7,5,3,2,19,19,11,2]] ["loop",[11,17,13,11,7,5,3,2,19,19,9]] ["loop",[19,17,13,11,7,5,3,2,19,11]] ["loop",[19,17,13,11,7,5,3,2,19]] ["loop",[19,17,13,11,7,5,3,2,19,19]] ["print","19 "] ["loop",[19,17,13,11,7,5,3,2,19]] ["loop",[19,17,13,11,7,5,3,2,19,0]] ["loop",[19,17,13,11,7,5,3,2,19,0,1]] ["loop",[19,17,13,11,7,5,3,2,19,-1]] ...
This may be used as a way to debug your program purely in terminal but after annotating the code you'll see that the printed stack is annotated too -- half of RASEL instructions are moving or creating new items on the stack and if the instruction cell had an annotation the stack item copies it.
... ["loop",[[19,"p"],[17,"p"],[13,"p"],[11,"p"],[7,"p"],[5,"p"],[3,"p"],[2,"p"],[19,"new candidate"],[11,"dummy"]]] ["loop",[[19,"p"],[17,"p"],[13,"p"],[11,"p"],[7,"p"],[5,"p"],[3,"p"],[2,"p"],[19,"new candidate"]]] ["loop",[[19,"p"],[17,"p"],[13,"p"],[11,"p"],[7,"p"],[5,"p"],[3,"p"],[2,"p"],[19,"new candidate"],[19,"new candidate"]]] ["print","19 "] ["loop",[[19,"p"],[17,"p"],[13,"p"],[11,"p"],[7,"p"],[5,"p"],[3,"p"],[2,"p"],[19,"new candidate"]]] ["loop",[[19,"p"],[17,"p"],[13,"p"],[11,"p"],[7,"p"],[5,"p"],[3,"p"],[2,"p"],[19,"new candidate"],0]] ...
The annotated code runner is currently hardcoded to halt on reaching specific time or print size limits. This is what the IDE launches when you press the "RUN" button. The annotated stack output is then parsed and displayed under the code editor as a "LOG:"
Computational class
There is a translator from Brainfuck to RASEL written in Ruby.
There is a pure RASEL interpreter of Brainfuck. The annotated .rasela
version is available in the same folder.