completed parser and reimplemented repl loop to C#

This commit is contained in:
Valerie Wolfe 2024-04-03 12:01:06 -04:00
parent e4ad03774e
commit 7e03710779
7 changed files with 138 additions and 51 deletions

View file

@ -1,14 +0,0 @@
def repl_loop():
while True:
raw = input("> ")
if raw == 'exit':
break
parts = raw.split(" ")
command = parts[0]
del parts[0]
if command in repl_commands:
repl_commands[command](parts)
repl_loop()

View file

@ -1,34 +1,40 @@
using System.Text.RegularExpressions;
using Dungeoneer.Interpreter;
namespace Dungeoneer {
public sealed class Command {
public static void Roll(IList<string> args) {
public static void Roll(TokenSet input) {
// don't do anything with empty expressions
if(args.Count == 0)
if(input.Count == 0)
return;
// pass to subcommands
var first = args[0];
if(Program.RollMacros.ContainsKey(first)) {
Program.RollMacros[first](args);
var first = input[0];
if(first is NameToken) {
string value = ((NameToken)first).Value;
var subset = new TokenSet(input);
subset.RemoveAt(0);
Program.RollMacros[value](subset);
return;
}
// create new parsed expression object and pretty-print
var roll = new RollExpression(args);
var result = roll.Result;
if(result == null)
Console.WriteLine("invalid expression");
else
Console.WriteLine($"{roll.Print}\n => {result}");
var roll = Dungeoneer.Interpreter.Parser.ParseRoll(input);
//var result = roll.Result;
Console.WriteLine($"{input}\n=> {roll}");
}
public static void New(IList<string> args) {
public static void New(TokenSet input) {
}
public static void LexTest(TokenSet input) {
Console.WriteLine(input);
}
}
}

View file

@ -6,8 +6,8 @@ namespace Dungeoneer.Interpreter {
public static class Lexer {
public static readonly char[] Delimiters = { ' ', '+', '-', '*', '/', '^' };
public static readonly char[] Operators = { '+', '-', '*', '/', '^' };
public static readonly char[] Delimiters = { ' ', '+', '-', '*', '/', '^', '%' };
public static readonly char[] Operators = { '+', '-', '*', '/', '^', '%' };
public static TokenSet Tokenize(string text) {
var output = new TokenSet();
@ -23,7 +23,6 @@ namespace Dungeoneer.Interpreter {
foreach(char c in text) {
if(Delimiters.Contains(c)) {
if(buffer.Length != 0) {
Console.WriteLine(buffer);
var token = Match(buffer);
output.Add(token);
}
@ -40,6 +39,16 @@ namespace Dungeoneer.Interpreter {
return output;
}
public static Token First(string text) {
string buffer = "";
foreach(char c in text)
if(Delimiters.Contains(c) && !Operators.Contains(c))
break;
else
buffer += c;
return Match(buffer);
}
public static Token Match(string text) {
var diceMatch = DiceToken.Match(text);
@ -58,6 +67,10 @@ namespace Dungeoneer.Interpreter {
if(varMatch.Success)
return new VarToken(varMatch);
var nameMatch = NameToken.Match(text);
if(nameMatch.Success)
return new NameToken(nameMatch);
throw new UnmatchedTokenException(text);
}
@ -66,6 +79,7 @@ namespace Dungeoneer.Interpreter {
public class TokenSet : List<Token> {
public TokenSet() : base() { }
public TokenSet(List<Token> set) : base(set) { }
public override string ToString() {
var output = "";
@ -111,6 +125,17 @@ namespace Dungeoneer.Interpreter {
}
public class NameToken : Token {
public string Value { get; private set; }
internal static readonly Regex Pattern = new Regex(@"^[A-Za-z][A-Za-z\d\-_]+$");
public NameToken(Match match) { Value = match.Groups[0].Value; }
public static Match Match(string text) { return Pattern.Match(text); }
}
public class VarToken : ValueToken {
private const string Style = "\x1b[33;1m";
@ -150,13 +175,13 @@ namespace Dungeoneer.Interpreter {
}
public override string ToString() {
var output = $"{Style}( ";
var output = $"{Style}(";
var result = Result;
for(int i = 0; i < Count; i++)
if(i < Count - 1)
output += $"{result[i]}, ";
else
output += $"{result[i]} ){Dungeoneer.Format.Reset}";
output += $"{result[i]}){Dungeoneer.Format.Reset}";
return output;
}

54
src/Parse.cs Normal file
View file

@ -0,0 +1,54 @@
using System;
namespace Dungeoneer.Interpreter {
public static class Parser {
public static int ParseRoll(string text) { return ParseRoll(Lexer.Tokenize(text)); }
public static int ParseRoll(TokenSet tokens) {
int result = 0;
bool wasValue = false;
char operation = '+';
int? rollDc = null;
foreach(Token token in tokens) {
switch(token) {
case ValueToken value:
if(wasValue)
throw new Exception("value -> value");
result = Operate(result, value.Value, operation);
wasValue = true;
break;
case OperatorToken op:
if(!wasValue)
throw new Exception("operator -> operator");
operation = op.Value;
wasValue = false;
break;
case DcToken dc:
if(rollDc.HasValue)
throw new Exception("two DC elements");
rollDc = dc.Value;
break;
default:
throw new Exception($"invalid token: {token.GetType()}");
}
}
return result;
}
public static int Operate(int a, int b, char op) {
switch(op) {
case '+': return (a + b);
case '-': return (a - b);
case '*': return (a * b);
case '/': return (a / b);
case '%': return (a % b);
case '^': return (int)Math.Pow(a, b);
default: throw new Exception("unmatched operator");
}
}
}
}

View file

@ -1,13 +1,16 @@
using System;
using Dungeoneer.Interpreter;
namespace Dungeoneer {
public sealed class Program {
public delegate void Command(IList<string> args);
public delegate void Command(TokenSet input);
public static Dictionary<string, Command> ReplCommands = new Dictionary<string, Command> {
{ "new", (Command)Dungeoneer.Command.New },
{ "roll", (Command)Dungeoneer.Command.Roll }
{ "roll", (Command)Dungeoneer.Command.Roll },
// { "lex", (Command)Dungeoneer.Command.LexTest }
// TODO: "with"?
};
@ -19,7 +22,25 @@ namespace Dungeoneer {
public static void Main() {
Console.WriteLine("Starting");
Scripting.Run("scripts/repl.py");
Repl();
}
public static void Repl() {
while(true) {
Console.Write("> ");
var raw = Console.ReadLine();
if(raw == "exit")
break;
var tokens = Lexer.Tokenize(raw);
var first = tokens[0];
tokens.RemoveAt(0);
if(first is NameToken) {
var command = (first as NameToken).Value;
ReplCommands[command](tokens);
} else {
Console.WriteLine($"no command '{raw}' found!");
}
}
}
}

View file

@ -1,7 +1,7 @@
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using Dungeoneer.Lexing;
using Dungeoneer.Interpreter;
namespace Dungeoneer {
@ -79,28 +79,15 @@ namespace Dungeoneer {
public static class RollMacro {
public static void Pool(IList<string> args) {
DiceToken? dice = null;
int? dc = null;
for(int i = 1; i < args.Count; i++) {
string arg = args[i];
var dieCheck = DiceToken.Match(arg);
if(dieCheck.Success)
dice = new DiceToken(dieCheck);
else
dc = int.Parse(arg);
}
var rolls = dice.Result;
bool success = false;
public static void Pool(TokenSet input) {
/*
foreach(var roll in rolls)
if(roll > dc.Value) {
success = true;
break;
}
var result = RollResult.Wrap(success);
Console.WriteLine($"{dice}\n => {result}");
Console.WriteLine($"{dice}\n => {result}");*/
}
}

View file

@ -1,9 +1,12 @@
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;
using IronPython.Hosting;
using Microsoft.Scripting.Hosting;
using Dungeoneer.Interpreter;
namespace Dungeoneer {
public static class Scripting {
@ -29,6 +32,11 @@ namespace Dungeoneer {
Scope.repl_commands = Program.ReplCommands;
Scope.roll_macros = Program.RollMacros;
Scope.interpret = new ExpandoObject();
Scope.interpret.tokenize = (Func<string, TokenSet>)Lexer.Tokenize;
Scope.interpret.first_token = (Func<string, Token>)Lexer.First;
// Scope.interpret.parse_roll = (Func<string, string>)Interpreter.Parser.ParseRoll;
// helper functions
Scope.roll = (Func<int, int>)Dungeoneer.Util.Roll;
}