mirror of
https://github.com/tonytins/BarkBasic.git
synced 2025-03-14 21:11:20 +00:00
Rewritten and simplified architecture
This commit is contained in:
parent
0fee8ccac9
commit
cf7bcaf032
9 changed files with 83 additions and 190 deletions
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Version>0.1.120</Version>
|
<Version>0.1.200</Version>
|
||||||
<VersionSuffix>alpha</VersionSuffix>
|
<VersionSuffix>alpha</VersionSuffix>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|
45
BarkBasic/BasicInterpreter.cs
Normal file
45
BarkBasic/BasicInterpreter.cs
Normal 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);
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 0.1.200
|
||||||
|
|
||||||
|
- Rewritten and simplified architecture.
|
||||||
|
|
||||||
## 0.1.120
|
## 0.1.120
|
||||||
|
|
||||||
Moved the hard-coded example to a ``.basic`` file that is read from a Toml-based project file.
|
Moved the hard-coded example to a ``.basic`` file that is read from a Toml-based project 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +1,37 @@
|
||||||
using BarkBasic;
|
using BarkBasic;
|
||||||
using Tomlyn;
|
|
||||||
|
|
||||||
var projModel = File.ReadAllText(Path.Combine(Environment.CurrentDirectory, "Project.toml"));
|
class Program
|
||||||
var projFile = Toml.ToModel<ProjectFile>(projModel);
|
|
||||||
var code = "";
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var path = Path.Combine(Environment.CurrentDirectory, projFile.Code);
|
static void Main()
|
||||||
if (File.Exists(path))
|
|
||||||
code = File.ReadAllText(path);
|
|
||||||
}
|
|
||||||
catch (Exception err)
|
|
||||||
{
|
|
||||||
throw new FileNotFoundException(err.StackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
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);
|
var interpreter = new BasicInterpreter();
|
||||||
continue;
|
interpreter.RegisterCommand("PRINT", PrintCommand);
|
||||||
}
|
interpreter.RegisterCommand("LET", LetCommand);
|
||||||
|
// Add more commands as needed
|
||||||
// TODO: Finish goto function.
|
|
||||||
var gotoMatch = gotoRegex.Match(line);
|
|
||||||
if (gotoMatch.Success)
|
|
||||||
{
|
|
||||||
/*using var reader = new StreamReader(projFile.Code);
|
|
||||||
|
|
||||||
for (var i = 1; i < int.Parse(gotoMatch.Groups[1].Value); i++)
|
string basicCode = @"
|
||||||
reader.ReadLine();
|
PRINT ""Hello, World!""
|
||||||
|
LET A = 10
|
||||||
Console.WriteLine(reader.ReadLine());*/
|
PRINT A
|
||||||
|
";
|
||||||
|
|
||||||
|
interpreter.Run(basicCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception($"Unknown statement in line: {line}");
|
static void PrintCommand(List<string> args)
|
||||||
}
|
{
|
||||||
|
Console.WriteLine(string.Join(" ", args));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void LetCommand(List<string> args)
|
||||||
|
{
|
||||||
|
if (args.Count != 2 || !int.TryParse(args[1], out int value))
|
||||||
|
{
|
||||||
|
Console.WriteLine("LET command syntax error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the variable and its value in a dictionary or context
|
||||||
|
Console.WriteLine($"Variable {args[0]} set to {value}");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
namespace BarkBasic;
|
|
||||||
|
|
||||||
internal record Token(TokenType Type, string? Value);
|
|
|
@ -1,3 +0,0 @@
|
||||||
namespace BarkBasic;
|
|
||||||
|
|
||||||
internal enum TokenType { Number, Plus, Minus, Multiply, Divide, LParen, RParen, Print, EOF }
|
|
|
@ -4,7 +4,9 @@ Bark Basic (working title) is a BASIC programming language paser. Right now, it
|
||||||
|
|
||||||
## Background
|
## 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
|
## License
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue