From 7e037107796b670d701ffbb01d54eca3c4d56528 Mon Sep 17 00:00:00 2001 From: Valerie Date: Wed, 3 Apr 2024 12:01:06 -0400 Subject: [PATCH] completed parser and reimplemented repl loop to C# --- scripts/repl.py | 14 ------------- src/Command.cs | 30 ++++++++++++++++----------- src/Lex.cs | 35 ++++++++++++++++++++++++++----- src/Parse.cs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ src/Program.cs | 27 +++++++++++++++++++++--- src/Roll.cs | 21 ++++--------------- src/Scripting.cs | 8 +++++++ 7 files changed, 138 insertions(+), 51 deletions(-) delete mode 100644 scripts/repl.py create mode 100644 src/Parse.cs diff --git a/scripts/repl.py b/scripts/repl.py deleted file mode 100644 index a118389..0000000 --- a/scripts/repl.py +++ /dev/null @@ -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() - diff --git a/src/Command.cs b/src/Command.cs index e90c171..5ab92d3 100644 --- a/src/Command.cs +++ b/src/Command.cs @@ -1,34 +1,40 @@ using System.Text.RegularExpressions; +using Dungeoneer.Interpreter; + namespace Dungeoneer { public sealed class Command { - public static void Roll(IList 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 args) { + public static void New(TokenSet input) { } + public static void LexTest(TokenSet input) { + Console.WriteLine(input); + } + } } diff --git a/src/Lex.cs b/src/Lex.cs index 9f1b43c..a67181e 100644 --- a/src/Lex.cs +++ b/src/Lex.cs @@ -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 { public TokenSet() : base() { } + public TokenSet(List 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; } diff --git a/src/Parse.cs b/src/Parse.cs new file mode 100644 index 0000000..1d8b7d4 --- /dev/null +++ b/src/Parse.cs @@ -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"); + } + } + + } + +} + diff --git a/src/Program.cs b/src/Program.cs index 71f0c2f..d36a37d 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -1,13 +1,16 @@ using System; +using Dungeoneer.Interpreter; + namespace Dungeoneer { public sealed class Program { - public delegate void Command(IList args); + public delegate void Command(TokenSet input); public static Dictionary ReplCommands = new Dictionary { { "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!"); + } + } } } diff --git a/src/Roll.cs b/src/Roll.cs index c9cdda1..340204a 100644 --- a/src/Roll.cs +++ b/src/Roll.cs @@ -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 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}");*/ } } diff --git a/src/Scripting.cs b/src/Scripting.cs index 846b605..95bf013 100644 --- a/src/Scripting.cs +++ b/src/Scripting.cs @@ -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)Lexer.Tokenize; + Scope.interpret.first_token = (Func)Lexer.First; + // Scope.interpret.parse_roll = (Func)Interpreter.Parser.ParseRoll; + // helper functions Scope.roll = (Func)Dungeoneer.Util.Roll; }