Initial commit. A lot already exists.
This commit is contained in:
commit
db025f648b
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
# Visual Studio Files
|
||||
.vs/
|
||||
|
||||
# Compilation Files
|
||||
BrRun/bin/
|
||||
BrRun/obj/
|
||||
25
BrRun.sln
Normal file
25
BrRun.sln
Normal 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
|
||||
11
BrRun/BrInterpretContext.cs
Normal file
11
BrRun/BrInterpretContext.cs
Normal 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() { }
|
||||
}
|
||||
15
BrRun/BrInterpreterBase.cs
Normal file
15
BrRun/BrInterpreterBase.cs
Normal 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
11
BrRun/BrRun.csproj
Normal 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
8
BrRun/InterpretMode.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace BrRun;
|
||||
|
||||
public enum InterpretMode
|
||||
{
|
||||
StandardBr,
|
||||
BrPlusPlus,
|
||||
UsefulBr,
|
||||
}
|
||||
309
BrRun/Interpreters/StandardBrInterpreter.cs
Normal file
309
BrRun/Interpreters/StandardBrInterpreter.cs
Normal 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
85
BrRun/Program.cs
Normal 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
7
Examples/add.br
Normal file
@ -0,0 +1,7 @@
|
||||
,
|
||||
>>++++++++++++++++++++++++++++++++.+++++++++++.-----------.<
|
||||
,
|
||||
>.+++++++++++++++++++++++++++++.-----------------------------.<
|
||||
|
||||
<[->+<]
|
||||
>.
|
||||
1
Examples/fibonacci.br
Normal file
1
Examples/fibonacci.br
Normal file
@ -0,0 +1 @@
|
||||
+[[>+>+<<-]>]
|
||||
1
Examples/indexcounter.br
Normal file
1
Examples/indexcounter.br
Normal file
@ -0,0 +1 @@
|
||||
+[>+<[>+<-]>]
|
||||
1
Examples/memfill.br
Normal file
1
Examples/memfill.br
Normal file
@ -0,0 +1 @@
|
||||
+[>+]
|
||||
9
Examples/mul.br
Normal file
9
Examples/mul.br
Normal file
@ -0,0 +1,9 @@
|
||||
++++++++++++++++++++++++++++++++>,
|
||||
<.++++++++++.----------.>>,
|
||||
<<.+++++++++++++++++++++++++++++.-----------------------------.
|
||||
|
||||
>>[>+>+<<-]
|
||||
|
||||
<[>>[>>+<<-]>[<<+>>-]<<[>+>+<<-]<-]
|
||||
|
||||
>>>>.
|
||||
1
Examples/powersof2.br
Normal file
1
Examples/powersof2.br
Normal file
@ -0,0 +1 @@
|
||||
+[[>++<-]>]
|
||||
7
README.md
Normal file
7
README.md
Normal 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.
|
||||
Loading…
x
Reference in New Issue
Block a user