Rabbitsfoot

From Esolang
Jump to navigation Jump to search

Rabbitsfoot is a language for manipulating lists of integers, designed by IFcoltransG for the Unofficial Esolangs Discord Server's inaugural "Esolang Buffet" event. Its goal is to make writing a sorting algorithm as confusing as possible. Its original spec provides the reader with the 'fun' challenge of figuring out how to get things done in it; depending on what experience you want, you might want to peruse that page before this one to avoid spoilers.

Name

Rabbitsfoot was named because rabbits' feet are said to be lucky charms, but with undertones of the expression "monkey's paw". In the eponymous short story, the monkey's paw granted wishes, but with a terrible cost each time. While on the surface Rabbitsfoot may seem welcoming, getting what you wish done comes at an price.

Execution Model

While Rabbitsfoot claims to have three different types of value and several different mechanisms for control flow... in reality, it is an imperative language built around an unbounded stack of equal-length vectors. The only control flow is that the language automatically loops through the Cartesian product of its input.

All Rabbitsfoot programs take a list of signed integers as input data, let's say n integers. The interpreter infers from the source code a certain size w, so that every array on the stack will have size w. Then the runtime iterates through all possible lists of w elements from the input, allowing repeats. For example, if the input were [1, 2, 3], and w were 2, then the program would operate on [1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [2, 3], [3, 1], [3, 2] and [3, 3], in that order. (Or at least, it would start iterating through those numbers, but the interpreter is allowed to replace elements in the input array with other values, so by the end the numbers might look completely different.) Each loop, the interpreter runs through the file from the start until it reaches a full stop. When the program terminates, the input integers are printed out, except they might have been overwritten to new numbers.

It's more accurate to say it loops over indices of the input rather than elements themselves; this is because at the end of each loop, it assigns a new value to each of those indices. Those new values come from the array on the top of the stack. When w = 2, the Rabbitsfoot interpreter effectively runs through every pair of indices in the program, and allows you to replace the values at those indices with new values. (This includes pairs where both indices are the same.)

Source code

Originally every command was going to be its own line, but a competition requirement was that the sorting algorithm had to fit in 100 bytes, and it was just too many newlines! So some commands allow you to omit trailing newlines, namely +, *, /, ,, ., [], <>, ~, |, !, and ?. The latter two commands don't do anything though.

Commands

A lot of the commands don't really do anything except throw errors, so I'll only cover the ones that I actually used in the sorting program. You can find all the rest on the GitHub repo or the competition page for round 1. That's also where you can find the reference interpreter.

Comments

Start a line with # or REM for it to be ignored. Almost ignored — see the = command for more info. (REM requires a trailing space, but # doesn't.)

Loop

The . command is used to end a loop. Every Rabbitsfoot program needs one. When it's reached, the top of stack is popped and its elements assigned to the current indices, then the program moves to the next list of indices. It's the only useful piece of control flow in the language.

The list of indices starts at [0, ..., 0, 0, 0], then goes to [0, ..., 0, 0, 1], eventually to [0, ..., 0, 0, n-1] and then [0, ..., 0, 1, 0] and so on. Although in practice there might only be two indices, so [0, 0].

Literal

[...] is used to push an array of integers onto the stack. The Rabbitsfoot interpreter infers how big the arrays on the stack should be from the literals you use in your program. For instance, [1 -1] pushes an array containing 1 and -1 onto the stack, and also tells the interpreter that all arrays should have 2 elements.

Arithmetic

+ and * are both commands that pop two arrays from the stack, then take the sum or the product respectively. They do this elementwise, so [a, b, c] and [d, e, f] would get combined into [a+d, b+e, c+f].

Input

, is used to push elements from the input list onto the stack. It takes the array of current indices for the loop, looks up each index, and pushes the resulting array onto the stack.

For example, if all of the arrays on the stack have size 4, and the user input was [-10, 4, 5, 0, 0, 0, 0], then on the first iteration , would push [-10, -10] each time it's run. If the input data hasn't been overwritten at the end of the first iteration, then on the second iteration , would push [-10, 4]. Then [-10, 5] on the third.

Compare

The - command pops an array, pushes the result of comparing each element with zero. If an element is positive, it's replaced with 1. If negative, it's replaced with -1. If 0, then it's replaced with 0. (You'd think it would be negation or subtraction, but that's instead achieved by multiplying by -1 then adding.)

Division

/ pops an array, then divides each element in the array by the length of the array, rounding the result down towards -∞.

Transpose

The ~ command pops w arrays, where w is the size of each array, then makes a w×w matrix. The array at the top of the stack becomes the top row of the matrix. It transposes the matrix by flipping it about its main top-left-to-bottom-right diagonal, then pushes the rows of the matrix back onto the stack in the same order they were originally. So the top row of the new matrix becomes the top of the stack.

Macros

These last commands are just for making code shorter and cutting down on repetition. @ repeats line 1 of the program. = runs the first comment of the program as code. They don't play nice if you combine them together or if you put them inside the code they're meant to run.

There are some other commands that should function, like % for shuffling a list and <...> for incrementing elements in the input data list. Also |, which caches a value the first time it's run, and every other time, it replaces the top of stack with the cached value. But I mostly only included them as red herrings.

Example programs

Cat

   ,.

You may notice that this cat program similar to the brainfuck cat program, except that in brainfuck it only works for one character. In Rabbitsfoot, it never changes the input data, so the input data is output unchanged.

Zeros

   [0].

This program simply overwrites each input number with 0.

Sorting algorithm

   ,[1 0]*,[0 1]*~+,
   REM [-1 -1]*+
   
   #fung sort
   #@ is push[xy][yx]
   #= is minus
   
   =-
   @
   =[1 -1]**
   @
   ++/.

Here is the program submitted to the competition judges by the language creator, as proof that a sorting algorithm was possible in 100 bytes. Line 1 pushes two copies of the current input pair, except the first-pushed copy has its entries swapped. The first comment contains code to subtract the value at top of stack from the value below it. The other three comments are as short a description of the algorithm as could fit in 100 bytes; as you can see, it uses an extremely simple sorting algorithm published by Stanley P. Y. Fung. The whole language was designed with the goal that it could only run Fung's sorting algorithm. The other comments describe what the @ and = macros are set to: the first line pushes an input pair swapped, followed by the input pair; the first comment subtracts. The rest of the program compares the elements of the input pair, then pushes [min max]. When the source code is iterated over every pair in order, it will execute Fung's simple sorting algorithm.

Turing completeness

Rabbitsfoot is not Turing complete; in fact, it's a total language. If the input has size n, each Rabbitsfoot command can only run a maximum of n times, because there are no other ways to loop.

External links