Rewritten and simplified architecture

This commit is contained in:
Tony Bark 2025-02-01 05:22:56 -05:00
parent 0fee8ccac9
commit cf7bcaf032
9 changed files with 83 additions and 190 deletions

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<Version>0.1.120</Version>
<Version>0.1.200</Version>
<VersionSuffix>alpha</VersionSuffix>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>

View file

@ -0,0 +1,45 @@
namespace BarkBasic;
public class BasicInterpreter
{
private readonly Dictionary<string, Command> _commands = new();
public void RegisterCommand(string name, Action<List<string>> action) => _commands[name] = new Command(action);
public void Run(string code)
{
var lines = code.Split('\n');
foreach (var line in lines)
{
ExecuteLine(line);
}
}
private void ExecuteLine(string line)
{
line = line.Trim();
if (string.IsNullOrEmpty(line)) return;
var parts = line.Split(' ');
var commandName = parts[0].ToUpper();
var args = new List<string>(parts);
args.RemoveAt(0); // Remove the command name from arguments
if (_commands.ContainsKey(commandName))
{
_commands[commandName].Execute(args);
}
else
{
Console.WriteLine($"Unknown command: {commandName}");
}
}
}
public class Command
{
private readonly Action<List<string>> _action;
public Command(Action<List<string>> action) => _action = action;
public void Execute(List<string> args) => _action.Invoke(args);
}

View file

@ -1,5 +1,9 @@
# Change Log
## 0.1.200
- Rewritten and simplified architecture.
## 0.1.120
Moved the hard-coded example to a ``.basic`` file that is read from a Toml-based project file.

View file

@ -1,38 +0,0 @@
namespace BarkBasic;
internal class Lexer(string input)
{
static readonly Regex NumberRegex = new(@"\d+");
static readonly Regex WhitespaceRegex = new(@"\s+");
int _pos = 0;
public Token GetNextToken()
{
while (true)
{
if (_pos >= input.Length)
return new Token(TokenType.EOF, null);
if (char.IsDigit(input[_pos]))
{
var match = NumberRegex.Match(input, _pos);
_pos += match.Length;
return new Token(TokenType.Number, match.Value);
}
if (!char.IsWhiteSpace(input[_pos]))
return input[_pos++] switch
{
'+' => new Token(TokenType.Plus, "+"),
'-' => new Token(TokenType.Minus, "-"),
'*' => new Token(TokenType.Multiply, "*"),
'/' => new Token(TokenType.Divide, "/"),
'(' => new Token(TokenType.LParen, "("),
')' => new Token(TokenType.RParen, ")"),
_ => throw new Exception("Error parsing input")
};
_pos += WhitespaceRegex.Match(input, _pos).Length;
}
}
}

View file

@ -1,96 +0,0 @@
namespace BarkBasic;
internal class Parser
{
readonly Lexer _lexer;
Token _currentToken;
public Parser(Lexer lexer)
{
_lexer = lexer;
_currentToken = _lexer.GetNextToken();
}
void Eat(TokenType tokenType)
{
if (_currentToken.Type == tokenType)
_currentToken = _lexer.GetNextToken();
else
throw new Exception("Error parsing input");
}
public int Parse()
{
var result = Term();
while (_currentToken.Type is TokenType.Plus or TokenType.Minus)
{
switch (_currentToken.Type)
{
case TokenType.Plus:
Eat(TokenType.Plus);
result += Term();
break;
case TokenType.Minus:
Eat(TokenType.Minus);
result -= Term();
break;
case TokenType.Print:
Eat(TokenType.Print);
break;
}
}
return result;
}
int Term()
{
var result = Factor();
while (_currentToken.Type is TokenType.Multiply or TokenType.Divide)
{
switch (_currentToken.Type)
{
case TokenType.Multiply:
Eat(TokenType.Multiply);
result *= Factor();
break;
case TokenType.Divide:
Eat(TokenType.Divide);
result /= Factor();
break;
}
}
return result;
}
int Factor()
{
var token = _currentToken;
switch (token.Type)
{
case TokenType.Number:
Eat(TokenType.Number);
return int.Parse(token.Value ?? throw new InvalidOperationException());
case TokenType.LParen:
{
Eat(TokenType.LParen);
var result = Parse();
Eat(TokenType.RParen);
return result;
}
case TokenType.Plus:
case TokenType.Minus:
case TokenType.Multiply:
case TokenType.Divide:
case TokenType.RParen:
case TokenType.Print:
case TokenType.EOF:
default:
throw new Exception("Error parsing input");
}
}
}

View file

@ -1,55 +1,37 @@
using BarkBasic;
using Tomlyn;
var projModel = File.ReadAllText(Path.Combine(Environment.CurrentDirectory, "Project.toml"));
var projFile = Toml.ToModel<ProjectFile>(projModel);
var code = "";
class Program
{
static void Main()
{
var interpreter = new BasicInterpreter();
interpreter.RegisterCommand("PRINT", PrintCommand);
interpreter.RegisterCommand("LET", LetCommand);
// Add more commands as needed
try
{
var path = Path.Combine(Environment.CurrentDirectory, projFile.Code);
if (File.Exists(path))
code = File.ReadAllText(path);
}
catch (Exception err)
{
throw new FileNotFoundException(err.StackTrace);
string basicCode = @"
PRINT ""Hello, World!""
LET A = 10
PRINT A
";
interpreter.Run(basicCode);
}
var lines = code.Split(Environment.NewLine);
var lineNumberRegex = new Regex(@"^\d+");
var commentRegex = new Regex(@"REM.*$");
var printRegex = new Regex(@"PRINT\s+'(.*)'");
var gotoRegex = new Regex(@"GOTO\s+(\d+)");
foreach (var line in lines)
static void PrintCommand(List<string> args)
{
if (string.IsNullOrWhiteSpace(line)) continue;
var lineNumberMatch = lineNumberRegex.Match(line);
if (!lineNumberMatch.Success) throw new Exception($"Invalid line number in line: {line}");
var commentMatch = commentRegex.Match(line);
if (commentMatch.Success) continue; // Ignore comments
var printMatch = printRegex.Match(line);
if (printMatch.Success)
{
Console.WriteLine(printMatch.Groups[1].Value);
continue;
Console.WriteLine(string.Join(" ", args));
}
// TODO: Finish goto function.
var gotoMatch = gotoRegex.Match(line);
if (gotoMatch.Success)
static void LetCommand(List<string> args)
{
/*using var reader = new StreamReader(projFile.Code);
for (var i = 1; i < int.Parse(gotoMatch.Groups[1].Value); i++)
reader.ReadLine();
Console.WriteLine(reader.ReadLine());*/
if (args.Count != 2 || !int.TryParse(args[1], out int value))
{
Console.WriteLine("LET command syntax error");
return;
}
throw new Exception($"Unknown statement in line: {line}");
// Store the variable and its value in a dictionary or context
Console.WriteLine($"Variable {args[0]} set to {value}");
}
}

View file

@ -1,3 +0,0 @@
namespace BarkBasic;
internal record Token(TokenType Type, string? Value);

View file

@ -1,3 +0,0 @@
namespace BarkBasic;
internal enum TokenType { Number, Plus, Minus, Multiply, Divide, LParen, RParen, Print, EOF }

View file

@ -4,7 +4,9 @@ Bark Basic (working title) is a BASIC programming language paser. Right now, it
## Background
I've always wanted to write a parser for a programming language, but I had no idea where to start because there are so many ways to write one. Despite writing varies ones in the past, I've never really felt satisfied. In the past, I had the free version of ChatGPT write me drafts for a few starting points, but those were as buggy as you would expect. So, I decided to give it another whirl using Bing Copilot instead. After a few attempts, I asked it to use my algorithm from [CST.NET](https://github.com/tonytins/cstdotnet) as a reference, and it worked!
I've always wanted to write a parser for a programming language, but I had no idea where to start because there are so many ways to write one. Despite writing varies ones in the past, I've never really felt satisfied, so I asked AI.
Originally, I used ChatGPT and I create a BASIC parser so I could use a template. While it worked, it was a little over-engineered for what I needed. Fast-forward to Deepseek making a splash, I tweaked the system prompt for the Coder version to be as good as NASA and requested the same thing but have it be expandable. Worked on the first try from [.NET Fiddle](https://dotnetfiddle.net/vPPVHD).
## License