Compare commits
No commits in common. "canary" and "main" have entirely different histories.
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,5 +2,5 @@
|
||||
.vs/
|
||||
|
||||
# Compilation Files
|
||||
*/bin/
|
||||
*/obj/
|
||||
BrRun/bin/
|
||||
BrRun/obj/
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
namespace BfRun
|
||||
{
|
||||
internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Hello, World!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ 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}") = "BfRun", "BfRun\BfRun.csproj", "{5FDEA610-020B-4D09-8950-491E551DA9A6}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BrRun", "BrRun\BrRun.csproj", "{2F98A27C-2F74-4377-BC63-88E1DF52558D}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@ -11,15 +11,15 @@ Global
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{5FDEA610-020B-4D09-8950-491E551DA9A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5FDEA610-020B-4D09-8950-491E551DA9A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5FDEA610-020B-4D09-8950-491E551DA9A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5FDEA610-020B-4D09-8950-491E551DA9A6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{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 = {22B5EFF7-3B38-4012-A9E7-ABD601F01ADC}
|
||||
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();
|
||||
}
|
||||
@ -3,8 +3,9 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<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,
|
||||
}
|
||||
325
BrRun/Interpreters/StandardBrInterpreter.cs
Normal file
325
BrRun/Interpreters/StandardBrInterpreter.cs
Normal file
@ -0,0 +1,325 @@
|
||||
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)> debugOpeningStack;
|
||||
|
||||
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 = [];
|
||||
debugOpeningStack = [];
|
||||
|
||||
lineNumber = 1;
|
||||
charNumber = 0;
|
||||
}
|
||||
|
||||
public override void Interpret()
|
||||
{
|
||||
awakeTop = Console.CursorTop;
|
||||
if (Context.stepFlag)
|
||||
{
|
||||
for (int i = 0; i < 10; i++) Console.WriteLine();
|
||||
awakeTop = Console.CursorTop - 10;
|
||||
}
|
||||
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 L{lineNumber} C{charNumber}: data pointer has overflowed! (length {TapeLength})");
|
||||
dataPointer = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case IntentionKind.DecrementPointer:
|
||||
dataPointer--;
|
||||
if (dataPointer < 0)
|
||||
{
|
||||
Console.WriteLine($"warn L{lineNumber} C{charNumber}: 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 L{lineNumber} C{charNumber}: file hasn't been opened yet! how did this happen?");
|
||||
else
|
||||
{
|
||||
stack.Push(reader.Position);
|
||||
debugOpeningStack.Push((lineNumber, charNumber));
|
||||
if (tape[dataPointer] == 0)
|
||||
{
|
||||
// Look for closing brace.
|
||||
IntentionKind newIntent;
|
||||
while ((newIntent = StepProgram()) != IntentionKind.EndOfFile)
|
||||
{
|
||||
if (newIntent == IntentionKind.EndGroup) break; // Found closing bracket.
|
||||
}
|
||||
if (newIntent == IntentionKind.EndOfFile)
|
||||
{
|
||||
Console.WriteLine($"error L{lineNumber} C{charNumber}: no closing bracket to match opening bracket.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case IntentionKind.EndGroup:
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
Console.WriteLine($"error L{lineNumber} C{charNumber}: no opening bracket to match closing bracket.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (tape[dataPointer] == 0)
|
||||
{
|
||||
// Exit loop.
|
||||
stack.Pop();
|
||||
debugOpeningStack.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Restart.
|
||||
if (reader is null) Console.WriteLine($"error L{lineNumber} C{charNumber}: file hasn't been opened yet! how did this happen?");
|
||||
else reader.Seek(stack.Peek(), SeekOrigin.Begin);
|
||||
|
||||
(int newL, int newC) = debugOpeningStack.Peek();
|
||||
lineNumber = newL;
|
||||
charNumber = newC;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
Console.WriteLine($"warn L{lineNumber} C{charNumber}: 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 => tape[dataPointer] == 0
|
||||
? "Skipping loop. Moving execution forward to closing bracket."
|
||||
: "Beginning a loop",
|
||||
IntentionKind.EndGroup => tape[dataPointer] == 0
|
||||
? $"Breaking out of a loop"
|
||||
: $"Moving execution back to L{debugOpeningStack.Peek().Item1} C{debugOpeningStack.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();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user