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 System.Text.RegularExpressions;
using Dungeoneer.Interpreter;
namespace Dungeoneer { namespace Dungeoneer {
public sealed class Command { public sealed class Command {
public static void Roll(IList<string> args) { public static void Roll(TokenSet input) {
// don't do anything with empty expressions // don't do anything with empty expressions
if(args.Count == 0) if(input.Count == 0)
return; return;
// pass to subcommands // pass to subcommands
var first = args[0]; var first = input[0];
if(Program.RollMacros.ContainsKey(first)) { if(first is NameToken) {
Program.RollMacros[first](args); string value = ((NameToken)first).Value;
var subset = new TokenSet(input);
subset.RemoveAt(0);
Program.RollMacros[value](subset);
return; return;
} }
// create new parsed expression object and pretty-print // create new parsed expression object and pretty-print
var roll = new RollExpression(args); var roll = Dungeoneer.Interpreter.Parser.ParseRoll(input);
var result = roll.Result; //var result = roll.Result;
if(result == null) Console.WriteLine($"{input}\n=> {roll}");
Console.WriteLine("invalid expression");
else
Console.WriteLine($"{roll.Print}\n => {result}");
} }
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 class Lexer {
public static readonly char[] Delimiters = { ' ', '+', '-', '*', '/', '^' }; public static readonly char[] Delimiters = { ' ', '+', '-', '*', '/', '^', '%' };
public static readonly char[] Operators = { '+', '-', '*', '/', '^' }; public static readonly char[] Operators = { '+', '-', '*', '/', '^', '%' };
public static TokenSet Tokenize(string text) { public static TokenSet Tokenize(string text) {
var output = new TokenSet(); var output = new TokenSet();
@ -23,7 +23,6 @@ namespace Dungeoneer.Interpreter {
foreach(char c in text) { foreach(char c in text) {
if(Delimiters.Contains(c)) { if(Delimiters.Contains(c)) {
if(buffer.Length != 0) { if(buffer.Length != 0) {
Console.WriteLine(buffer);
var token = Match(buffer); var token = Match(buffer);
output.Add(token); output.Add(token);
} }
@ -40,6 +39,16 @@ namespace Dungeoneer.Interpreter {
return output; 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) { public static Token Match(string text) {
var diceMatch = DiceToken.Match(text); var diceMatch = DiceToken.Match(text);
@ -58,6 +67,10 @@ namespace Dungeoneer.Interpreter {
if(varMatch.Success) if(varMatch.Success)
return new VarToken(varMatch); return new VarToken(varMatch);
var nameMatch = NameToken.Match(text);
if(nameMatch.Success)
return new NameToken(nameMatch);
throw new UnmatchedTokenException(text); throw new UnmatchedTokenException(text);
} }
@ -66,6 +79,7 @@ namespace Dungeoneer.Interpreter {
public class TokenSet : List<Token> { public class TokenSet : List<Token> {
public TokenSet() : base() { } public TokenSet() : base() { }
public TokenSet(List<Token> set) : base(set) { }
public override string ToString() { public override string ToString() {
var output = ""; 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 { public class VarToken : ValueToken {
private const string Style = "\x1b[33;1m"; private const string Style = "\x1b[33;1m";
@ -150,13 +175,13 @@ namespace Dungeoneer.Interpreter {
} }
public override string ToString() { public override string ToString() {
var output = $"{Style}( "; var output = $"{Style}(";
var result = Result; var result = Result;
for(int i = 0; i < Count; i++) for(int i = 0; i < Count; i++)
if(i < Count - 1) if(i < Count - 1)
output += $"{result[i]}, "; output += $"{result[i]}, ";
else else
output += $"{result[i]} ){Dungeoneer.Format.Reset}"; output += $"{result[i]}){Dungeoneer.Format.Reset}";
return output; 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 System;
using Dungeoneer.Interpreter;
namespace Dungeoneer { namespace Dungeoneer {
public sealed class Program { 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> { public static Dictionary<string, Command> ReplCommands = new Dictionary<string, Command> {
{ "new", (Command)Dungeoneer.Command.New }, { "new", (Command)Dungeoneer.Command.New },
{ "roll", (Command)Dungeoneer.Command.Roll } { "roll", (Command)Dungeoneer.Command.Roll },
// { "lex", (Command)Dungeoneer.Command.LexTest }
// TODO: "with"? // TODO: "with"?
}; };
@ -19,7 +22,25 @@ namespace Dungeoneer {
public static void Main() { public static void Main() {
Console.WriteLine("Starting"); 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.Security.Cryptography;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dungeoneer.Lexing; using Dungeoneer.Interpreter;
namespace Dungeoneer { namespace Dungeoneer {
@ -79,28 +79,15 @@ namespace Dungeoneer {
public static class RollMacro { public static class RollMacro {
public static void Pool(IList<string> args) { public static void Pool(TokenSet input) {
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;
foreach(var roll in rolls) foreach(var roll in rolls)
if(roll > dc.Value) { if(roll > dc.Value) {
success = true; success = true;
break; break;
} }
var result = RollResult.Wrap(success); 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.Collections.Generic;
using System.Dynamic;
using System.Reflection; using System.Reflection;
using IronPython.Hosting; using IronPython.Hosting;
using Microsoft.Scripting.Hosting; using Microsoft.Scripting.Hosting;
using Dungeoneer.Interpreter;
namespace Dungeoneer { namespace Dungeoneer {
public static class Scripting { public static class Scripting {
@ -29,6 +32,11 @@ namespace Dungeoneer {
Scope.repl_commands = Program.ReplCommands; Scope.repl_commands = Program.ReplCommands;
Scope.roll_macros = Program.RollMacros; 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 // helper functions
Scope.roll = (Func<int, int>)Dungeoneer.Util.Roll; Scope.roll = (Func<int, int>)Dungeoneer.Util.Roll;
} }