Compass Soup
Interpreter: C++
Compass Soup is kind of like a Turing machine with an infinite 2-dimensional tape. The main catch is that the instruction memory and data memory are in the same space, and the output of the program is the entire contents of that space.
How it works
A program is a 2-dimensional block of text. The program space starts with the entire source code placed with the first character at (0,0). The rest of the program space is infinite and is initialized with null characters (ASCII 0).
There are two pointers that can move around the program space:
- The execution pointer has a location and a direction (North, South, East, or West). Each tick, the instruction under the execution pointer is executed, then the execution pointer moves in its current direction. The execution pointer starts moving right (positive x), at the location of the
!
character or at (0,0) if that doesn't exist. - The data pointer has only a location. It is moved with the instructions
x
,X
,y
, andY
. It starts at the location of the@
character or at (0,0) if that doesn't exist.
Input
The contents of stdin are printed into the program space starting at the location of the >
character, or at (0,0) if that doesn't exist.
Output
The program terminates when the execution pointer runs irretrievably out of bounds. The output is the entire contents of the program space at that time. It is sent to stdout and 'result.txt'.
Instructions
n
- redirects the execution pointer North (negative y)e
- redirects the execution pointer East (positive x)s
- redirects the execution pointer South (positive y)w
- redirects the execution pointer West (negative x)y
- moves the data pointer North (negative y)X
- moves the data pointer East (positive x)Y
- moves the data pointer South (positive y)x
- moves the data pointer West (negative x)p
- writes the next character encountered by the execution pointer at the data pointer. That character isn't executed as an instruction.j
- checks the next character encountered by the execution pointer against the character under the data pointer. That character isn't executed as an instruction. If they are the same, the execution pointer jumps over the next character.c
- writes the null character at the data pointer.*
- breakpoint - just causes the interpreter to break.
All other characters are ignored by the execution pointer.
Interpreter
The interpreter takes the source file as an argument and input on stdin. It has a steppable debugger, which you can invoke with a breakpoint instruction in the code (*
). When broken, the execution pointer is shown as ASCII 178 (darker shaded block) and the data pointer is shown as ASCII 177 (lighter shaded block).
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <stdio.h>
// Compass Soup programming language interpreter
// created by Brian MacIntosh (BMacZero)
// for http://codegolf.stackexchange.com/questions/61804/create-a-programming-language-that-only-appears-to-be-unusable
//
// 31 October 2015
struct Point
{
int x, y;
Point(int ix, int iy) { x = ix; y = iy; };
bool operator==(const Point &other) const
{
return other.x == x && other.y == y;
}
bool operator!=(const Point &other) const
{
return other.x != x || other.y != y;
}
};
struct Bounds
{
int xMin, xMax, yMin, yMax;
Bounds(int xmin, int ymin, int xmax, int ymax)
{
xMin = xmin; yMin = ymin; xMax = xmax; yMax = ymax;
}
bool contains(Point pt)
{
return pt.x >= xMin && pt.x <= xMax && pt.y >= yMin && pt.y <= yMax;
}
int getWidth() { return xMax - xMin + 1; }
int getHeight() { return yMax - yMin + 1; }
bool operator==(const Bounds &other) const
{
return other.xMin == xMin && other.xMax == xMax && other.yMin == yMin && other.yMax == yMax;
}
bool operator!=(const Bounds &other) const
{
return other.xMin != xMin || other.xMax != xMax || other.yMin != yMin || other.yMax != yMax;
}
};
int max(int a, int b) { return a > b ? a : b; }
int min(int a, int b) { return a < b ? a : b; }
Bounds hull(Point a, Bounds b)
{
return Bounds(min(a.x, b.xMin), min(a.y, b.yMin), max(a.x, b.xMax), max(a.y, b.yMax));
}
Bounds hull(Bounds a, Bounds b)
{
return Bounds(min(a.xMin, b.xMin), min(a.yMin, b.yMin), max(a.xMax, b.xMax), max(a.yMax, b.yMax));
}
Bounds programBounds(0,0,0,0);
char** programSpace;
Point execPtr(0,0);
Point execPtrDir(1,0);
Point dataPtr(0,0);
Point stdInPos(0,0);
bool breakpointHit = false;
char breakOn = 0;
/// reads the character from the specified position
char read(Point pt)
{
if (programBounds.contains(pt))
return programSpace[pt.x - programBounds.xMin][pt.y - programBounds.yMin];
else
return 0;
}
/// read the character at the data pointer
char readData()
{
return read(dataPtr);
}
/// read the character at the execution pointer
char readProgram()
{
return read(execPtr);
}
/// gets the bounds of the actual content of the program space
Bounds getTightBounds(bool debug)
{
Bounds tight(0,0,0,0);
for (int x = programBounds.xMin; x <= programBounds.xMax; x++)
{
for (int y = programBounds.yMin; y <= programBounds.yMax; y++)
{
if (read(Point(x, y)) != 0)
{
tight = hull(Point(x, y), tight);
}
}
}
if (debug)
{
tight = hull(dataPtr, tight);
tight = hull(execPtr, tight);
}
return tight;
}
/// ensure that the program space encompasses the specified rectangle
void fitProgramSpace(Bounds bounds)
{
Bounds newBounds = hull(bounds, programBounds);
if (newBounds == programBounds) return;
// allocate new space
char** newSpace = new char*[newBounds.getWidth()];
// copy content
for (int x = 0; x < newBounds.getWidth(); x++)
{
newSpace[x] = new char[newBounds.getHeight()];
for (int y = 0; y < newBounds.getHeight(); y++)
{
Point newWorldPos(x + newBounds.xMin, y + newBounds.yMin);
newSpace[x][y] = read(newWorldPos);
}
}
// destroy old space
for (int x = 0; x < programBounds.getWidth(); x++)
{
delete[] programSpace[x];
}
delete[] programSpace;
programSpace = newSpace;
programBounds = newBounds;
}
/// outputs the current program space to a file
void outputToStream(std::ostream &stream, bool debug)
{
Bounds tight = getTightBounds(debug);
for (int y = tight.yMin; y <= tight.yMax; y++)
{
for (int x = tight.xMin; x <= tight.xMax; x++)
{
char at = read(Point(x, y));
if (debug && x == execPtr.x && y == execPtr.y)
stream << (char)178;
else if (debug && x == dataPtr.x && y == dataPtr.y)
stream << (char)177;
else if (at == 0)
stream << ' ';
else
stream << at;
}
stream << std::endl;
}
}
/// writes a character at the specified position
void write(Point pt, char ch)
{
fitProgramSpace(hull(pt, programBounds));
programSpace[pt.x - programBounds.xMin][pt.y - programBounds.yMin] = ch;
}
/// writes a character at the data pointer
void write(char ch)
{
write(dataPtr, ch);
}
/// writes a line of text horizontally, starting at the specified position
void writeLine(Point loc, std::string str, bool isSource)
{
fitProgramSpace(Bounds(loc.x, loc.y, loc.x + str.size(), loc.y));
for (unsigned int x = 0; x < str.size(); x++)
{
programSpace[x + loc.x][loc.y] = str[x];
// record locations of things
if (isSource)
{
switch (str[x])
{
case '>':
stdInPos = Point(loc.x + x, loc.y);
break;
case '!':
execPtr = Point(loc.x + x, loc.y);
break;
case '@':
dataPtr = Point(loc.x + x, loc.y);
break;
}
}
}
}
void advanceExecPtr()
{
execPtr.x += execPtrDir.x;
execPtr.y += execPtrDir.y;
}
void breakpoint()
{
breakpointHit = true;
outputToStream(std::cout, true);
std::cout << "[Return]: step | [Space+Return]: continue | [<char>+Return]: continue to <char>" << std::endl;
while (true)
{
std::string input;
std::getline(std::cin, input);
if (input.size() == 0)
{
break;
}
else if (input.size() == 1)
{
if (input[0] == ' ')
{
breakpointHit = false;
break;
}
else
{
breakOn = input[0];
breakpointHit = false;
break;
}
}
}
}
int main(int argc, char** argv)
{
if (argc != 2)
{
printf("Usage: CompassSoup <source-file>");
return 1;
}
// open source file
std::ifstream sourceIn(argv[1]);
if (!sourceIn.is_open())
{
printf("Error reading source file.");
return 1;
}
programSpace = new char*[1];
programSpace[0] = new char[1];
programSpace[0][0] = 0;
// read starting configuration
std::string line;
int currentLine = 0;
while (std::getline(sourceIn, line))
{
writeLine(Point(0, currentLine), line, true);
currentLine++;
}
sourceIn.close();
// take stdin
std::string input;
std::cout << ">";
std::cin >> input;
std::cin.ignore();
writeLine(stdInPos, input, false);
// execute
while (programBounds.contains(execPtr))
{
if (execPtr.x == 0 && execPtr.y == 0)
{
printf("Implementation error: execPtr is stuck.");
break;
}
advanceExecPtr();
char command = readProgram();
// breakpoint control code
if (breakpointHit || (breakOn != 0 && command == breakOn))
{
breakOn = 0;
breakpoint();
}
switch (command)
{
case 'n':
execPtrDir = Point(0,-1);
break;
case 'e':
execPtrDir = Point(1,0);
break;
case 's':
execPtrDir = Point(0,1);
break;
case 'w':
execPtrDir = Point(-1,0);
break;
case 'x':
dataPtr.x--;
break;
case 'X':
dataPtr.x++;
break;
case 'y':
dataPtr.y--;
break;
case 'Y':
dataPtr.y++;
break;
case 'p':
advanceExecPtr();
write(readProgram());
break;
case 'j':
advanceExecPtr();
if (readData() == readProgram())
{
advanceExecPtr();
}
break;
case 'c':
write(0);
break;
case '*':
breakpoint();
break;
}
}
std::ofstream outputFile("result.txt");
outputToStream(outputFile, false);
outputToStream(std::cout, false);
outputFile.close();
}
Example
Input: a string of characters terminated by a zero character. Outputs yes
on the first line of the output if the number of 1s in the input is odd, otherwise outputs |
.
|>
!*ceXj1s-c-eXj0s-c-exj|s-pyXpeXps
c | c | | |
cn0j-w---n1j-w n---w
Tips
You should use a good text editor and make judicious use of the functionality of the 'Insert' key, and use 'Alt-Drag' to add or delete text on multiple rows at once.