Initial commit. A lot already exists.

This commit is contained in:
That_One_Nerd 2024-04-11 19:49:18 -04:00
commit db025f648b
15 changed files with 497 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
# Visual Studio Files
.vs/
# Compilation Files
BrRun/bin/
BrRun/obj/

25
BrRun.sln Normal file
View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34309.116
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrRun", "BrRun\BrRun.csproj", "{2F98A27C-2F74-4377-BC63-88E1DF52558D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2F98A27C-2F74-4377-BC63-88E1DF52558D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F98A27C-2F74-4377-BC63-88E1DF52558D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F98A27C-2F74-4377-BC63-88E1DF52558D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F98A27C-2F74-4377-BC63-88E1DF52558D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {777E29D0-1222-4B40-BCB3-BD0489E5F38D}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,11 @@
namespace BrRun;
public class BrInterpretContext
{
public required string filePath;
public required bool stepFlag;
public required bool usefulFlag;
public required InterpretMode mode;
internal BrInterpretContext() { }
}

View File

@ -0,0 +1,15 @@
namespace BrRun;
public abstract class BrInterpreterBase
{
public BrInterpretContext Context { get; set; }
public string FilePath { get; set; }
public BrInterpreterBase(string filePath, BrInterpretContext context)
{
FilePath = filePath;
Context = context;
}
public abstract void Interpret();
}

11
BrRun/BrRun.csproj Normal file
View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>bfrun</AssemblyName>
</PropertyGroup>
</Project>

8
BrRun/InterpretMode.cs Normal file
View File

@ -0,0 +1,8 @@
namespace BrRun;
public enum InterpretMode
{
StandardBr,
BrPlusPlus,
UsefulBr,
}

View File

@ -0,0 +1,309 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace BrRun.Interpreters;
public class StandardBrInterpreter : BrInterpreterBase
{
// Can technically be changed, but it should remain the same to match convention.
public static readonly short TapeMinValue = 0, TapeMaxValue = 255;
public static readonly int TapeLength = 30_000;
protected FileStream? reader;
protected readonly short[] tape;
protected int dataPointer;
protected Stack<long> stack;
protected Stack<(int, int)> debugStack;
private int lineNumber;
private int charNumber;
private char curChar;
private int awakeTop = 0;
public StandardBrInterpreter(string filePath, BrInterpretContext context) : base(filePath, context)
{
reader = null;
tape = new short[TapeLength];
dataPointer = 0;
stack = [];
debugStack = [];
lineNumber = 1;
charNumber = 0;
}
public override void Interpret()
{
awakeTop = Console.CursorTop;
if (Context.stepFlag)
{
for (int i = 0; i < 12; i++) Console.WriteLine();
awakeTop = Console.CursorTop - 12;
}
reader = new(FilePath, FileMode.Open);
IntentionKind intent;
while ((intent = StepProgram()) != IntentionKind.EndOfFile)
{
if (Context.stepFlag) ShowDebugScreen(intent);
HandleIntent(intent);
}
reader.Close();
Console.CursorVisible = true;
}
protected void HandleIntent(IntentionKind intent)
{
switch (intent)
{
case IntentionKind.IncrementPointer:
dataPointer++;
if (dataPointer >= TapeLength)
{
Console.WriteLine($"warn: data pointer has overflowed! (length {TapeLength})");
dataPointer = 0;
}
break;
case IntentionKind.DecrementPointer:
dataPointer--;
if (dataPointer < 0)
{
Console.WriteLine($"warn: data pointer has underflowed! (length {TapeLength})");
dataPointer = TapeLength - 1;
}
break;
case IntentionKind.IncrementValue:
if (tape[dataPointer] == TapeMaxValue) tape[dataPointer] = TapeMinValue;
else tape[dataPointer]++;
break;
case IntentionKind.DecrementValue:
if (tape[dataPointer] == TapeMinValue) tape[dataPointer] = TapeMaxValue;
else tape[dataPointer]--;
break;
case IntentionKind.OutputValue:
Console.Write((char)tape[dataPointer]);
break;
case IntentionKind.InputValue:
Console.CursorVisible = true;
tape[dataPointer] = (byte)Console.ReadKey().KeyChar;
break;
case IntentionKind.BeginGroup:
if (reader is null) Console.WriteLine("error: file hasn't been opened yet! how did this happen?");
else
{
stack.Push(reader.Position);
debugStack.Push((lineNumber, charNumber));
}
break;
case IntentionKind.EndGroup:
if (stack.Count == 0)
{
Console.WriteLine("error: no opening bracket to match closing bracket.");
return;
}
if (tape[dataPointer] == 0)
{
// Exit loop.
stack.Pop();
debugStack.Pop();
}
else
{
// Restart.
if (reader is null) Console.WriteLine("error: file hasn't been opened yet! how did this happen?");
else reader.Seek(stack.Peek(), SeekOrigin.Begin);
(int newL, int newC) = debugStack.Peek();
lineNumber = newL;
charNumber = newC;
}
break;
default:
Console.WriteLine("warn: unknown intent! how did this happen?");
break;
}
}
private int remainingSkips = 0;
protected void ShowDebugScreen(IntentionKind currentIntent)
{
const int numSpace = 3;
Console.CursorVisible = false;
int initialTop = Console.CursorTop, initialLeft = Console.CursorLeft;
int totalCanFit = (Console.WindowWidth - 1) / (numSpace + 3);
int startIndex = int.Max(0, (int)(dataPointer - totalCanFit * 0.5)),
endIndex = int.Min(startIndex + totalCanFit - 1, TapeLength - 1);
int consolePos = 0;
for (int i = startIndex; i <= endIndex; i++)
{
Console.SetCursorPosition(consolePos, awakeTop + 1);
Console.Write($" {i,numSpace + 2}");
Console.SetCursorPosition(consolePos, awakeTop + 2);
if (i == startIndex) Console.Write("╔═════");
else Console.Write("╦═════");
Console.SetCursorPosition(consolePos, awakeTop + 3);
Console.Write($"║ {tape[i],numSpace} ");
Console.SetCursorPosition(consolePos, awakeTop + 4);
if (i == startIndex) Console.Write("╚═════");
else Console.Write("╩═════");
Console.SetCursorPosition(consolePos, awakeTop + 5);
if (i == dataPointer) Console.Write(" ^ ");
else Console.Write(" ");
Console.SetCursorPosition(consolePos, awakeTop + 9);
Console.Write($"──────");
consolePos += numSpace + 3;
}
Console.SetCursorPosition(consolePos, awakeTop + 2);
Console.Write('╗');
Console.SetCursorPosition(consolePos, awakeTop + 3);
Console.Write('║');
Console.SetCursorPosition(consolePos, awakeTop + 4);
Console.Write('╝');
Console.SetCursorPosition(consolePos, awakeTop + 9);
Console.Write('─');
Console.SetCursorPosition(0, awakeTop + numSpace + 3);
string message = $"L{lineNumber} C{charNumber}: {curChar} {GetDebugDescriptionOfOperator(currentIntent)} ";
Console.Write(message + new string(' ', int.Max(Console.WindowWidth - message.Length - 1, 0)));
if (remainingSkips < int.MaxValue - 1) remainingSkips--;
if (remainingSkips == int.MaxValue - 1 && currentIntent == IntentionKind.EndGroup && tape[dataPointer] == 0) remainingSkips = 0;
Console.SetCursorPosition(0, awakeTop + 7);
if (currentIntent == IntentionKind.InputValue)
{
Console.Write("Enter one character to step the program." + new string(' ', 70));
}
else
{
if (remainingSkips > 0)
{
string message2;
if (remainingSkips == int.MaxValue) message2 = $"Continuing program to completion...";
else if (remainingSkips == int.MaxValue - 1) message2 = $"Waiting for loop exit...";
else message2 = $"Skipping {remainingSkips} steps...";
Console.Write(message2 + new string(' ', 110 - message2.Length));
}
else
{
Console.Write("Press space to step the system. D = +5 steps, F = +25 steps, G = +100 steps, J = until loop ends, K = continuous");
_readKey:
ConsoleKeyInfo stepKey = Console.ReadKey(true);
switch (stepKey.Key)
{
case ConsoleKey.Spacebar:
remainingSkips++;
break;
case ConsoleKey.D:
remainingSkips += 5;
break;
case ConsoleKey.F:
remainingSkips += 25;
break;
case ConsoleKey.G:
remainingSkips += 100;
break;
case ConsoleKey.J:
remainingSkips = int.MaxValue - 1;
break;
case ConsoleKey.K:
remainingSkips = int.MaxValue;
break;
default: goto _readKey;
}
}
}
Console.SetCursorPosition(initialLeft, initialTop);
}
private string GetDebugDescriptionOfOperator(IntentionKind intent) => intent switch
{
IntentionKind.IncrementPointer => $"Move pointer to right ({dataPointer} -> {dataPointer + 1})",
IntentionKind.DecrementPointer => $"Move pointer to left ({dataPointer} -> {dataPointer - 1})",
IntentionKind.IncrementValue => $"Increase value at position {dataPointer} ({tape[dataPointer]} -> {tape[dataPointer] + 1})",
IntentionKind.DecrementValue => $"Decrease value at position {dataPointer} ({tape[dataPointer]} -> {tape[dataPointer] - 1})",
IntentionKind.OutputValue => $"Print out current value as character (value {tape[dataPointer]})",
IntentionKind.InputValue => $"Input next character input into position {dataPointer}",
IntentionKind.BeginGroup => "Beginning a loop",
IntentionKind.EndGroup => tape[dataPointer] == 0
? $"Breaking out of group"
: $"Moving execution back to L{debugStack.Peek().Item1} C{debugStack.Peek().Item2} until value at position {dataPointer} is zero (currently {tape[dataPointer]})",
_ => "?? unknown intent ??"
};
protected IntentionKind StepProgram()
{
if (reader is null)
{
if (reader is null) Console.WriteLine("error: file hasn't been opened yet! how did this happen?");
return IntentionKind.EndOfFile;
}
int cI = reader.ReadByte();
if (cI == -1) return IntentionKind.EndOfFile;
char c = (char)cI;
if (c == '\n')
{
lineNumber++;
charNumber = 0;
}
else if (c != '\r') charNumber++;
curChar = c;
switch (c)
{
case '\r' or '\n' or ' ' or '\t': return StepProgram(); // Skip newlines.
case '#':
Console.WriteLine("error: comments are not supported in standard brainfuck.");
return StepProgram();
case '>': return IntentionKind.IncrementPointer;
case '<': return IntentionKind.DecrementPointer;
case '+': return IntentionKind.IncrementValue;
case '-': return IntentionKind.DecrementValue;
case '.': return IntentionKind.OutputValue;
case ',': return IntentionKind.InputValue;
case '[': return IntentionKind.BeginGroup;
case ']': return IntentionKind.EndGroup;
default:
Console.WriteLine($"error: unsupported operator {c}");
return StepProgram();
}
}
protected enum IntentionKind
{
EndOfFile,
IncrementPointer,
DecrementPointer,
IncrementValue,
DecrementValue,
OutputValue,
InputValue,
BeginGroup,
EndGroup
}
}

85
BrRun/Program.cs Normal file
View File

@ -0,0 +1,85 @@
using BrRun.Interpreters;
using System;
using System.IO;
namespace BrRun;
public static class Program
{
public static void Main(string[] args)
{
if (args.Length == 0)
{
Console.WriteLine("fatal: no file provided.");
return;
}
string path = args[0];
bool stepFlag = false, usefulFlag = false;
for (int i = 1; i < args.Length; i++)
{
switch (args[i])
{
case "--step":
if (stepFlag) Console.WriteLine("warn: duplicate --step flag.");
stepFlag = true;
break;
case "--useful":
if (usefulFlag) Console.WriteLine("warn: duplicate --useful flag.");
usefulFlag = true;
break;
default:
Console.WriteLine($"warn: unknown {args[i]} argument.");
break;
}
}
if (!File.Exists(path))
{
Console.WriteLine($"fatal: file does not exist at {path}");
return;
}
InterpretMode mode;
if (path.EndsWith(".bf") || path.EndsWith(".br") || path.EndsWith(".b"))
{
mode = InterpretMode.StandardBr;
if (usefulFlag) Console.WriteLine("warn: --useful flag is not applicable to standard brainfuck.");
}
else if (path.EndsWith(".bpp") || path.EndsWith(".b++") || path.EndsWith(".bfpp") ||
path.EndsWith(".bf++") || path.EndsWith(".brpp") || path.EndsWith(".br++"))
{
mode = InterpretMode.BrPlusPlus;
if (usefulFlag) mode = InterpretMode.UsefulBr;
}
else
{
Console.WriteLine($"fatal: unsupported file type {path[path.LastIndexOf('.')..]}.");
return;
}
BrInterpretContext context = new()
{
filePath = path,
stepFlag = stepFlag,
usefulFlag = usefulFlag,
mode = mode
};
BrInterpreterBase interpreter;
switch (mode)
{
case InterpretMode.StandardBr:
interpreter = new StandardBrInterpreter(path, context);
break;
default:
Console.WriteLine("fatal: unknown interpreter mode. how did this happen?");
return;
}
interpreter.Interpret();
}
}

7
Examples/add.br Normal file
View File

@ -0,0 +1,7 @@
,
>>++++++++++++++++++++++++++++++++.+++++++++++.-----------.<
,
>.+++++++++++++++++++++++++++++.-----------------------------.<
<[->+<]
>.

1
Examples/fibonacci.br Normal file
View File

@ -0,0 +1 @@
+[[>+>+<<-]>]

1
Examples/indexcounter.br Normal file
View File

@ -0,0 +1 @@
+[>+<[>+<-]>]

1
Examples/memfill.br Normal file
View File

@ -0,0 +1 @@
+[>+]

9
Examples/mul.br Normal file
View File

@ -0,0 +1,9 @@
++++++++++++++++++++++++++++++++>,
<.++++++++++.----------.>>,
<<.+++++++++++++++++++++++++++++.-----------------------------.
>>[>+>+<<-]
<[>>[>>+<<-]>[<<+>>-]<<[>+>+<<-]<-]
>>>>.

1
Examples/powersof2.br Normal file
View File

@ -0,0 +1 @@
+[[>++<-]>]

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# BrRun - Your cross-platform Brainfuck Interpreter
It's obvious just how useful brainfuck is. Hundreds of platforms and organizations use it in their backend development and it's become normal for aprogrammer looking for a job to include their brainfuck experience in their portfolio. Brainfuck is truly the language that powers our daily technology, and its successor brainfuck++ (the definition of which is detailed in a paper presented at ACH's SIGBOVIK 2022, the proceedings of which can be found [here](https://sigbovik.org/2022/proceedings.pdf)) is the backbone of the modern internet.
However, one common issue is that there exists no single mainstream compiler or interpreter for brainfuck and its ++ variant. Many independant individuals have developed their own interpreters to fit their needs, but very little consensus has been formed around which specific interpreter or compiler to use. This is where BrRun enteres the ring. The goal of BrRun is to generalize and standardize the use of brainfuck and brainfuck++ across all platforms. The interpreter is currently written in C#, with future plans to rewrite the interpreter in brainfuck itself<sup>[citation needed]</sup>. The interpreter is incredibly easy to use, with built-in future-proofed debugging technology. In the command line, simply invoke the bfrun.exe file, supply the brainfuck or brainfuck++ file to execute as the first argument, and include the `--step` flag if you intend to utilize the state-of-the-art debugging tools.
BfRun is the diamond in the rough, the one interpreter to rule all brainfuck development environments. This elegant software will tear down mega-corporations and truly improve the world for the individual. Be the first to acknowledge and accept this change by starring this repository. Please donate to the cause at <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" style="color: #F55142;">URL Damaged or Missing</a>. Accept with open arms this new world order or risk becoming obsolete. Download the interpreter. Download the interpreter.