RASEL

From Esolang
Jump to navigation Jump to search
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.

empty source file

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.

annotated prime numbers generator example

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:"

IDE log start
IDE log middle (around the 'print 19' event)

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.