Mastermind

From Esolang
Jump to navigation Jump to search

Mastermind is a programming language designed specifically to create Brainfuck code. The language and compiler was created by Heath Manning in 2023 as a tool for creating optimized Brainfuck programs [1]. The Mastermind IDE includes a Rust-based compiler, which converts (compiles) Mastermind source programs into Brainfuck. The current Mastermind compiler generates Brainfuck code assuming an infinite bi-directional tape, as well as 8-bit wrapping cells [1].

Design

The language design is analogous to C, except the compilation target is Brainfuck, instead of a real-world instruction set or practical intermediary representation. Most notably, programmers are able to embed Brainfuck code within Mastermind, and vice-versa, analogous to C's in-line Assembly.

Usage

Variables & Values

Variables can be defined as single cells, or as contiguous sets of cells:

// single-cell:
let var = 56;
let c = 'g';
let bool = true; // true/false equivalent to 1/0
// multi-cell:
let array[4] = [1, 2, 3, 4];
let string[5] = "hello";

If left uninitialised, a cell is assumed to have a value of 0.

Expressions consist of simple adds and subtractions, arrays and strings cannot be used in expressions:

// supported:
var += 4 + (5 - 4 + (3 - 2));
var = 'g' - 23 + true;
var = var + 5; // inefficient but supported
var += array[0] + 5;
let arr[3] = [4 + 3 - ('5' - 46), 1, 3];

// NOT supported:
var += [4, 5, 7][1] + 3;

Array indices must be compile-time constant integers

Input & Output

Single bytes can be output using the output operator:

output 'h';
output var;
output var + 6;
output array[0];
// the following are equivalent:
output '\n';
output 10;

Likewise for input:

input var;
input arr[2];

There is also * spread syntax and support for strings and arrays for ease of use:

output "hello";
output ['1', '2', '3'];
output *array;

input *array; 

Loops

The simplest is the while loop, which only supports cell references, not expressions:

while var {
   // do stuff
   // var -= 1;
   // etc
}

Draining loops

The "drain" loop is equivalent to the common Brainfuck construction of repeating an action N times, where N is the value of a tape cell. Hence, "draining" the cell while doing the actions in the loop body.

drain var {

}
// shorthand for following:
while var {
   // do stuff
   var -= 1;
}

This destructively loops as many times as the value in the cell being referenced, this can be used with expressions:

drain 10 {}
drain var - 6 {} 

There is also shorthand "into" syntax for adding to other cells:

drain var into other_var other_var_2 *spread_array etc;

// example of typical "for loop":
let i;
drain 10 into i {
   output '0' + i; // inefficient for the example
}
// "0123456789"
// equivalent to the following:
let i;
let N = 10;
while N {
   output '0' + i;
}

Copying loops

The "copy" loop is equivalent to the drain loop, except it is not destructive. This means the variable can be accessed safely from inside the loop body.

copy var into other_var *spread_var etc;

// examples:
copy var {
   // this will output the original var value, var times
   output var;
}

let rows = 3;
let columns = 6;
let total;
drain rows {
   copy columns into total {
      output '.';
   }
}
// ......
// ......
// ......

If/Else

The "if" and "else" keywords operate similar to C, except "else if" is not supported. The code in the "if" body will be executed if the provided expression is positive, otherwise the "else" body is executed. If the "not" keyword is used, the behavior is reversed. Examples:

if 13 {
   output "13";
}

if var {
   output "true";
} else {
   output "false";
}

// typical equivalence use-case:
if not var - 10 {
   // ==
} else {
   // !=
}

Functions

Functions work like templates/macros, as they do not perform any passing by value. All functions are in-lined at compile time. This means multiple calls to a large function will significantly increase your compiled Brainfuck size.

For this reason, function arguments are given using < angled bracket > syntax, much like generic functions in other languages:

def quote<arg> {
   output 39; // ASCII single quote
   output arg;
   output 39;
}

let N = 'g';
quote<N>;
N += 3;
quote<N>;
// gj

Imports

Imports work much like the C preprocessor:

#include "other_file"

This copies the contents of "other_file" into the current file at compile-time.

In-line Brainfuck features

In-line Brainfuck allows the programmer to define custom behavior as if writing raw Brainfuck, this is analogous to C's in-line Assembly.

// This is its most basic form:
// find the next cell that equals -1
bf {
   +[->+]-
}

// This is its more advanced form:
// input a line of lowercase letters and output the uppercase version
// this is an intentionally inefficient example
bf @3 clobbers var *spread_var etc {
   ,----------[++++++++++>,----------]
   <[<]>
   [
      {
         let g @0;
         assert g unknown;
         output g + ('A' - 'a');
         // embedded Mastermind!
      }
      >
   ]
   // now clear and return
   <[[-]<]>
}

It is the programmer's responsibility to clear used cells and return back to the cell in which the in-line Brainfuck context began. If the programmer does not do this, any Mastermind code after the in-line Brainf*** command will likely break.

Memory location specifiers

For hand-tuning optimizations and in-line Brainfuck that reads from Mastermind variables, you can specify the location on the Brainfuck tape: Variables can be fixed to a specified location on the eventual Brainfuck tape. This can also be done with in-line Brainfuck code sections. This can be used to share values between Mastermind and Brainf*** contexts.

let var @3 = 4;
// compiled: >>>++++

bf @4 {
   <><><>
}
// compiled: >>>><><><>

Clobbering and Assertions

The Mastermind compiler will try to predict the value of cells at compile-time, so it can prevent unnecessary cell clean-ups and unreachable code (with optimizations turned on). If your in-line Brainf*** affects existing Mastermind variables, you should tell the compiler using the "clobbers" keyword, the syntax is similar to the drain into variable list:

bf clobbers var *spread_var other_var etc {}

The compiler will now assume nothing about the values of those variables afterwards.

If instead you want to tell the compiler specifically that a value has become certain, you can use assert:

assert var equals 3;
// most common use cases:
assert var equals 0;
assert var unknown;

Asserting a variable as unknown is equivalent to clobbering.

Embedded Mastermind

Embedding Mastermind into your in-line Brainf*** allows you to use Mastermind syntax features for programs within your Brainf***, this is useful for N-length string based programs, or anything not possible in pure Mastermind:

let sum @0;

bf @0 {
   >>
   // read input (until eof) to the tape, nullifying any spaces or newlines
   // (this is probably not a good practical example, ideas are appreciated)
   ,[
      {
         let c @0;
         assert c unknown; // needed otherwise the compiler assumes c = 0

         if not (c - '\n') {
            c = 0;
         }
         if not (c - ' ') {
            c = 0;
         }
      }
      >,
   ]
}

Memory location specifiers are relative to the current Mastermind context. Also, top-level variables are not cleared by default in Mastermind contexts, this allows you to "leave" variables in cells for your Brainf*** to use. If you want your embedded Mastermind to clean itself up, you can simply open a scope at the top level:

bf {
   ++----++[][][<><><>] // the program doesn't matter for this example
   {
      // variables here will not be cleared
      let g @2;
      assert g unknown;
      {
         // variables here will be cleared
         let b = 32;
      }
   }
   {{
      // self-cleaning Mastermind code here
   }}
}

Advanced

All Mastermind features are available within the embedded Mastermind contexts, including in-line Brainfuck code.

bf {
   ++++[
      {
         let i @0;
         assert i unknown;
         let j @1 = i + 1;

         bf @1 {
            [.+]
            {
               // even more layers are possible
               bf {
                  {
                     output "h"
                  }
               }
            }
         }
      }
   -]
}

References