MashedPotatoes

From Esolang
Jump to navigation Jump to search

MashedPotatoes is a register-based esoteric programming language written by User:Mercerenies that is designed to look like other languages at a glance. It borrows many syntactic constructs from other languages but uses them for unusual purposes.

Overview

A MashedPotatoes program consists of a sequence of statements. There are three registers, three labels, and three stacks into which data can be stored. The registers are referred to as ^A (the accumulator), ^C (the counter), and ^E (the evaluator). The labels and stacks are simply numbered from 0 to 2 and are referred to by their index. Additionally, some statements can store peripheral data in their local state.

Syntax

The syntax of MashedPotatoes is designed, fittingly, to be a mash-up of the syntax of various other languages. As such, it is very ad hoc. In MashedPotatoes, whitespace is completely ignored. Whitespace is used in this article for the sake of readability, but it is unnecessary. There are certain statements whose behavior depends on the current line number, but aside from that, whitespace is always ignored.

To ensure that programs do not look too mundane, many expressions and statements must be referred to by different names depending on their placement in the code. If a statement has multiple forms listed, they must be used in cyclic order. That is, the first form must be used the first time a statement appears in the program. The second time it appears, the second form must be used, and so on. If the statement appears more times than there are forms in the list, it becomes necessary to loop back around to the first. For instance, Label 0 is defined as follows.

0.0f
++i
<>
Object

This should be read as such: the first time you wish to refer to Label 0, you should call it 0.0f. The second time you wish to do so, you should call it ++i, then <>, then Object. The fifth time, you will loop back around to 0.0f, and so on. It is a syntax error to refer to an entity by the wrong name. Note that this cyclic order refers to the order in which the parser reads the labels, not the order in which the evaluator executes them, so it doesn't matter if the name is in a loop or not; all that matters is that when reading the code from top to bottom, the names listed above appear in that order.

Labels

Labels are not statements, but they are often used as arguments to statements. There are three labels available. A label can hold either a numerical value or a reference to a code block. If a label holds a reference to a code block, the code block can be triggered by using either the Trigger A or Trigger B statements to do so. Different code blocks will respond differently to these two triggers.

Standard Labels

0.0f
++i
<>
Object

These names refer to Label 0. See the above section on cyclic order for when to use which name.

std::ignore
nullptr
__dict__
void(0)

These names refer to Label 1, again in cyclic order.

$ARGV
*read-eval*
FS
()

These names refer to Label 2.

Null Label

t

This is a special label value whose behavior is comparable to that of /dev/null on Unix systems. Any attempt to assign a value to t will be a no-op, and any attempt to read the value of t will result in an unbound variable error. This is most useful in a loop, if all you wish to do is repeat a block of code and do not need to name the loop.

Statements

These are the building blocks of any MashedPotatoes program. Remember that whitespace is mostly ignored; the forms provided here include whitespace for readability alone. Code that is placed in italics is intended to be replaced by the appropriate syntactic construct, so EXPR is intended to be replaced by an expression.

Label Binding Statements

synchronized (LABEL) {
  STMTS
}

This block executes STMTS in order, with the value of LABEL bound to the line number of the opening parenthesis ( (the first line is considered to be line 1, not 0). Note that this is the only time that whitespace makes a difference in MashedPotatoes: the numerical label binding depends on the current line number.

(format LABEL "
  STMTS
")

This is the basic looping construct in MashedPotatoes. It will execute the statements STMTS in a loop until the value of ^C is less than or equal to zero. At the end of each loop iteration, ^C is automatically decremented by one. Within the loop body, LABEL refers to the loop control code block. Sending Trigger A to the label will immediately break out of the loop, and sending Trigger B to the label will jump to the next loop iteration. In the latter case, ^C is still decremented.

proc LABEL {EXPR} {
  STMTS
}

This statement allows printing to the console. The statements STMTS are executed, with LABEL bound to a printing block. If the label is triggered, the expression EXPR will be evaluated, and its result will be printed to the console. On Trigger A, the resulting value will be treated as a Unicode code point (truncating if necessary) and its corresponding character will be printed to the screen. On Trigger B, the resulting value will be printed as a number in base 10.

case LABEL of {
  _ -> STMTS
}

This statements allows reading input from the user. The statements STMTS are executed, with LABEL bound to a reading block. If the label is triggered, input will be read from STDIN and stored into ^A. On Trigger A, a line of user input will be read and parsed as an integer. On Trigger B, a single character of user input will be read, and its code point will be used as the result.

WHILE LABEL OP EXPR
  STMTS
WEND

This statement allows arithmetic to be done. When the block is entered, LABEL is bound to an evaluator code block, the value of ^E is stored in the statement itself, and EXPR is evaluated and used as the new value of ^E. Then the statements STMTS are evaluated. At the end of the block, the statement's stored value is placed back in ^E. The behavior of triggers on LABEL depends on the OP argument, which should be one of the following.

OP value Trigger A Trigger B
< Add ^E to the stored value Subtract ^E from the stored value
> Divide the stored value by ^E Multiply the stored value by ^E
= Decrement the stored value Increment the stored value

Trigger Statements

use strict qw/LABEL/;
SETLOCAL LABEL
import LABEL;
CFLAGS=LABEL
\emph{LABEL}
`cat LABEL`

This statement sends Trigger A to LABEL. Like other statements with multiple forms, appearances of this statement must occur cyclically in order within the code. It is an error if the label is unbound or is bound to a numeric value.

s/LABEL//g
OUTPUT = LABEL
guard LABEL
lambda: LABEL
"""LABEL"""

This statement sends Trigger B to LABEL. Like other statements with multiple forms, appearances of this statement must occur cyclically in order within the code. It is an error if the label is unbound or is bound to a numeric value.

Register Statements

std::cout << EXPR << std::endl;

This relatively simple statement evaluates the expression and stores the result in ^A, overwriting any previous value in that register.

goto LABEL;

Rotates the three registers. If the label has an even value, rotates the values ^A -> ^C -> ^E -> ^A. If the label has an odd value, rotates the values in the opposite order ^A <- ^C <- ^E <- ^A. It is an error if a goto uses an unbound label or a label whose value is not numeric.

Stack Statements

def LABEL(EXPR)
  STMTS
end

This is the generalized stack manipulation command. The statements STMTS are executed, with LABEL bound to a stack manipulation code block. When the label is triggered, the expression EXPR is evaluated. Its numerical value is used (modulo 3) to determine the stack on which to operate. When Trigger A is used, ^A will be pushed onto the given stack. When Trigger B is used, ^A will be popped off the given stack. If the stack is empty, the value popped off will default to zero.

Comment Statements

:S(TEXT)
:F(TEXT)

Comments can appear anywhere that a statement can appear and will be ignored. Comment blocks, like many statements in MashedPotatoes, must occur cyclically, so the first comment should use the :S() form, the second should use the :F() form, the third :S(), and so on. The text inside the comment block can contain anything, including newlines, with the caveat that any parentheses must be balanced within the comment.

Expressions

Unlike statements, expressions return a value.

Register Expressions

It is useful to remember that all of the register expressions cycle through their values in ^A -> ^C -> ^E (remember: "Ace") order, simply with different starting points.

STDERR

This expression refers to the value of ^A at its first lexical appearance, then ^C the next time it is read by the parser, then ^E, then ^A and so on.

$[

This expression refers to the value of ^C at its first lexical appearance, then ^E the next time it is read by the parser, then ^A, then ^C and so on.

`uniq -c`

This expression refers to the value of ^E at its first lexical appearance, then ^A the next time it is read by the parser, then ^C, then ^E and so on.

Numerical Expressions

--help

This expressions always evaluates to the numerical value zero.

"Hello, world!"
arr[:]

These are two separate expressions (they are not cyclic forms of the same expression). Each expression evaluates to zero at its first lexical appearance, then one at its next, then two, and so on. It increments each time it appears in the code lexically.

Compound Expressions

(int) LABEL

This expression returns the numerical value of the label LABEL. It is an error if the label is unbound or non-numeric.

@{[ EXPR ]}

This expression evaluates the inner expression EXPR and returns its additive inverse. That is, it negates the value.

Examples

Factorial

This is a factorial function written in MashedPotatoes. It takes input from STDIN and outputs N! to STDOUT.

synchronized (std::ignore) { :S(We need a label with an odd value so we can rotate the registers)
  :F(This is merely to increment the value of "Hello, world!")
  std::cout << "Hello, world!" << std::endl;
  :S(Read in from the user)
  case 0.0f of {
    _ -> use strict qw/++i/;
  }
  :F(Rotate the registers)
  goto nullptr;
  :S(Put the constant 1 into ^A)
  std::cout << "Hello, world!" << std::endl;
  :F(Rotate again; this is equivalent to the previous goto)
  goto __dict__;
  :S(Loop N times)
  (format t "
    :F(Multiply the expression value by the loop counter)
    WHILE $ARGV > $[
      s/*read-eval*//g
    WEND
  ")
  :S(Output the result)
  proc <> { `uniq -c` } {
    OUTPUT = Object
  }
}

Fibonacci Sequence

Likewise, here is the Fibonacci sequence written in MashedPotatoes. Once again, it takes a single numerical value as input and prints the Nth Fibonacci number. If you run this code, ensure that you do not accidentally insert any newlines into it. The first synchronized block should be on an odd numbered line, and the rest should be on even numbered lines.

synchronized (std::ignore) {
  std::cout << "Hello, world!" << std::endl;
  case 0.0f of {
    _ -> use strict qw/++i/;
  }
  goto nullptr;
  std::cout << "Hello, world!" << std::endl;
  def $ARGV(--help)
    SETLOCAL *read-eval*
    import FS;
  end
  goto __dict__;
  (format t "
    def ()(--help)
      s/$ARGV//g
      goto void(0);
    end
    goto std::ignore;
    def *read-eval*(--help)
      OUTPUT = FS
    end
    goto nullptr;
    WHILE <> < --help
      goto __dict__;
      CFLAGS=Object
      synchronized (void(0)) {
        goto std::ignore;
      }
    WEND
    def ()(--help)
      \emph{$ARGV}
    end synchronized (nullptr) {
      goto __dict__;
      def *read-eval*(--help) `cat FS` end
    }
    goto void(0);
  ")
  def ()(--help)
    guard $ARGV
    lambda: *read-eval*
    proc 0.0f { STDERR } {
      """++i"""
    }
  end
}

Implementation

The canonical implementation is written in Python and is available on GitHub.

Computational Class

MashedPotatoes is believed to be Turing complete, as it has multiple stacks and can perform arbitrary arithmetic and loops. This has not yet been proven, however.