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 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
31
src/Lex.cs
31
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<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";
|
||||
|
|
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 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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
21
src/Roll.cs
21
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<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}");*/
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Reference in a new issue