completed parser and reimplemented repl loop to C#
This commit is contained in:
parent
e4ad03774e
commit
7e03710779
7 changed files with 138 additions and 51 deletions
|
@ -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()
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
35
src/Lex.cs
35
src/Lex.cs
|
@ -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
54
src/Parse.cs
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
21
src/Roll.cs
21
src/Roll.cs
|
@ -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}");*/
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue