Compare commits

...

1 Commits
main ... canary

Author SHA1 Message Date
62b829a11d New BF interpreter. 2025-02-04 18:05:51 -05:00
9 changed files with 19 additions and 454 deletions

4
.gitignore vendored
View File

@ -2,5 +2,5 @@
.vs/
# Compilation Files
BrRun/bin/
BrRun/obj/
*/bin/
*/obj/

View File

@ -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}") = "BrRun", "BrRun\BrRun.csproj", "{2F98A27C-2F74-4377-BC63-88E1DF52558D}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BfRun", "BfRun\BfRun.csproj", "{5FDEA610-020B-4D09-8950-491E551DA9A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -11,15 +11,15 @@ Global
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
{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
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {777E29D0-1222-4B40-BCB3-BD0489E5F38D}
SolutionGuid = {22B5EFF7-3B38-4012-A9E7-ABD601F01ADC}
EndGlobalSection
EndGlobal

View File

@ -3,9 +3,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>disable</ImplicitUsings>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>bfrun</AssemblyName>
</PropertyGroup>
</Project>

10
BfRun/Program.cs Normal file
View File

@ -0,0 +1,10 @@
namespace BfRun
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
}

View File

@ -1,11 +0,0 @@
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

@ -1,15 +0,0 @@
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();
}

View File

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

View File

@ -1,325 +0,0 @@
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
}
}

View File

@ -1,85 +0,0 @@
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();
}
}