Compare commits

..

3 commits

7 changed files with 112 additions and 103 deletions

View file

@ -9,6 +9,14 @@ namespace Dungeoneer.Error {
} }
public sealed class SyntaxException : Exception {
public SyntaxException(string message)
: base(message)
{ }
}
} }

View file

@ -76,11 +76,19 @@ namespace Dungeoneer.Interpreter {
} }
/** <summary> convenience object class for tokenized input </summary> */
public class TokenSet : List<Token> { public class TokenSet : List<Token> {
public TokenSet() : base() { } public TokenSet() : base() { }
public TokenSet(List<Token> set) : base(set) { } public TokenSet(List<Token> set) : base(set) { }
/** <summary> returns the first token in the set and removes it. </summary> */
public Token Pop() {
Token output = this[0];
RemoveAt(0);
return output;
}
public override string ToString() { public override string ToString() {
var output = ""; var output = "";
foreach(Token token in this) foreach(Token token in this)
@ -96,18 +104,39 @@ namespace Dungeoneer.Interpreter {
} }
public abstract class MetadataToken<T> : Token { /** <summary> a token that contains a value. </summary> */
public abstract class DataToken<T> : Token {
public T Value { get; protected set; } public T Value { get; protected set; }
} }
public abstract class ValueToken : Token { /** <summary> a token that is ignored during arithmetic evaluation. </summary> */
public int Value { get; protected set; } public abstract class MetadataToken<T> : DataToken<T> {
public T Value { get; protected set; }
public override string ToString() { return Value.ToString(); }
} }
public class OperatorToken : Token { /** <summary> a token with a numerical value </summary> */
public char Value; public abstract class ValueToken : DataToken<int> {
public override string ToString() { return Value.ToString(); }
}
/** <summary> a token with a string value </summary> */
public abstract class StringToken : DataToken<string> {
private const string Style = "\x1b[92m";
internal static readonly Regex Pattern = new Regex(@"^'(.+)'$");
public StringToken(Match match) { Value = match.Groups[1].Value; }
public override string ToString() { return $"{Style}'{Value}'{Format.Reset}"; }
public static Match Match(string text) { return Pattern.Match(text); }
}
/** <summary> a token representing an arithmetic operator. </summary> */
public class OperatorToken : DataToken<char> {
public OperatorToken(char op) { Value = op; } public OperatorToken(char op) { Value = op; }
@ -115,6 +144,7 @@ namespace Dungeoneer.Interpreter {
} }
/** <summary> a token representing a numeric literal </summary> */
public class NumberToken : ValueToken { public class NumberToken : ValueToken {
internal static readonly Regex Pattern = new Regex(@"^(\d+)$"); internal static readonly Regex Pattern = new Regex(@"^(\d+)$");
@ -125,8 +155,7 @@ namespace Dungeoneer.Interpreter {
} }
public class NameToken : Token { public class NameToken : DataToken<string> {
public string Value { get; private set; }
internal static readonly Regex Pattern = new Regex(@"^[A-Za-z][A-Za-z\d\-_]+$"); internal static readonly Regex Pattern = new Regex(@"^[A-Za-z][A-Za-z\d\-_]+$");

View file

@ -2,6 +2,8 @@ using System.Dynamic;
namespace Dungeoneer.Objects { namespace Dungeoneer.Objects {
public class Character { public class Character {
public string Name; public string Name;

View file

@ -1,5 +1,7 @@
using System; using System;
using Dungeoneer.Error;
namespace Dungeoneer.Interpreter { namespace Dungeoneer.Interpreter {
public static class Parser { public static class Parser {
@ -14,23 +16,23 @@ namespace Dungeoneer.Interpreter {
switch(token) { switch(token) {
case ValueToken value: case ValueToken value:
if(wasValue) if(wasValue)
throw new Exception("value -> value"); throw new SyntaxException("Value followed by value");
result = Operate(result, value.Value, operation); result = Operate(result, value.Value, operation);
wasValue = true; wasValue = true;
break; break;
case OperatorToken op: case OperatorToken op:
if(!wasValue) if(!wasValue)
throw new Exception("operator -> operator"); throw new SyntaxException("Operator followed by operator");
operation = op.Value; operation = op.Value;
wasValue = false; wasValue = false;
break; break;
case DcToken dc: case DcToken dc:
if(rollDc.HasValue) if(rollDc.HasValue)
throw new Exception("two DC elements"); throw new SyntaxException("Duplicate element: DC");
rollDc = dc.Value; rollDc = dc.Value;
break; break;
default: default:
throw new Exception($"invalid token: {token.GetType()}"); throw new SyntaxException($"invalid token: {token.GetType()}");
} }
} }
return result; return result;
@ -44,7 +46,7 @@ namespace Dungeoneer.Interpreter {
case '/': return (a / b); case '/': return (a / b);
case '%': return (a % b); case '%': return (a % b);
case '^': return (int)Math.Pow(a, b); case '^': return (int)Math.Pow(a, b);
default: throw new Exception("unmatched operator"); default: throw new SyntaxException("unmatched operator");
} }
} }

View file

@ -35,7 +35,9 @@ namespace Dungeoneer {
tokens.RemoveAt(0); tokens.RemoveAt(0);
if(first is NameToken) { if(first is NameToken) {
var command = (first as NameToken).Value; var command = (first as NameToken).Value;
ReplCommands[command](tokens); if(ReplCommands.ContainsKey(command))
try { ReplCommands[command](tokens); }
catch(Exception e) { Console.WriteLine($"error: {e.Message}"); }
} else { } else {
Console.WriteLine($"no command '{raw}' found!"); Console.WriteLine($"no command '{raw}' found!");
} }

View file

@ -1,93 +1,44 @@
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Dungeoneer.Error;
using Dungeoneer.Interpreter; using Dungeoneer.Interpreter;
namespace Dungeoneer { namespace Dungeoneer {
public class RollResult {
public dynamic Value { get; private set; }
private RollResult() { Value = null; }
public RollResult(string expression) {
Value = Scripting.Expr($"eval('{expression}')");
}
internal static class Style {
internal const string Default = "\x1b[1m";
internal const string BoolTrue = "\x1b[32;1m";
internal const string BoolFalse = "\x1b[31;1m";
}
public override string ToString() {
string style;
switch(Value) {
case bool boolValue:
style = boolValue ? Style.BoolTrue : Style.BoolFalse;
break;
default:
style = Style.Default;
break;
}
return $"{style}{Value}{Format.Reset}";
}
public static RollResult Wrap(dynamic value) {
var output = new RollResult();
output.Value = value;
return output;
}
}
public class RollExpression {
private IList<string> Parts;
public string Print { get; private set; }
public string Expression { get; private set; }
public RollResult Result {
get {
try { return new RollResult(Expression); }
catch { return null; }
}
}
public RollExpression(IList<string> parts) {
this.Parts = parts;
this.Print = "";
this.Expression = "";
// build expression from string parts
foreach(string piece in Parts) {
// die expression substitution
var dieCheck = DiceToken.Match(piece);
if(dieCheck.Success) {
var token = new DiceToken(dieCheck);
this.Print += $"{token} ";
this.Expression += $"{token.Value} ";
} else {
var part = $"{piece} ";
this.Expression += part;
this.Print += part;
}
}
}
}
public static class RollMacro { public static class RollMacro {
public static void Pool(TokenSet input) { public static void Pool(TokenSet input) {
/* if(input.Count != 2)
foreach(var roll in rolls) throw new SyntaxException($"expected 2 arguments ({input.Count} given)");
if(roll > dc.Value) {
DiceToken dicePool = null;
DcToken target = null;
foreach(var token in input) {
switch(token) {
case DiceToken dice:
if(dicePool != null)
throw new SyntaxException("two dice");
dicePool = dice;
break;
case DcToken dc:
if(target != null)
throw new SyntaxException("two dc values");
target = dc;
break;
}
}
bool success = false;
foreach(var roll in dicePool.Result) {
if(roll > target.Value) {
success = true; success = true;
break; break;
} }
var result = RollResult.Wrap(success); }
Console.WriteLine($"{dice}\n => {result}");*/
string message = success ? $"{Format.Green}pass" : $"{Format.Red}fail";
Console.WriteLine($"{dicePool} > {target}\n => {message}{Format.Reset}");
} }
} }

View file

@ -19,36 +19,51 @@ namespace Dungeoneer {
Scope = Engine.CreateScope(); Scope = Engine.CreateScope();
dynamic builtin = Engine.GetBuiltinModule(); dynamic builtin = Engine.GetBuiltinModule();
// set up imports
var paths = Engine.GetSearchPaths();
paths.Add("/usr/lib/python3.12/");
Engine.SetSearchPaths(paths);
// set up python environment // set up python environment
builtin.SetVariable("input", (Func<string, string>)Input); builtin.SetVariable("input", (Func<string, string>)Input); // `input` in IP is broken; swap for our implementation
builtin.RemoveVariable("open"); builtin.RemoveVariable("open"); // remove 'open' builtin; don't trust scripts with file i/o
// helper vars // helper vars
Scope.repl_commands = Program.ReplCommands; Scope.repl_commands = Program.ReplCommands; // repl command dictionary
Scope.roll_macros = Program.RollMacros; Scope.roll_macros = Program.RollMacros; // roll macro dictionary
// interpreter api object
Scope.interpret = new ExpandoObject(); Scope.interpret = new ExpandoObject();
Scope.interpret.tokenize = (Func<string, TokenSet>)Lexer.Tokenize; Scope.interpret.tokenize = (Func<string, TokenSet>)Lexer.Tokenize;
Scope.interpret.input = (Func<string,TokenSet>)LexInput;
Scope.interpret.first_token = (Func<string, Token>)Lexer.First; Scope.interpret.first_token = (Func<string, Token>)Lexer.First;
// Scope.interpret.parse_roll = (Func<string, string>)Interpreter.Parser.ParseRoll; Scope.interpret.parse_roll = (Func<string, int>)Interpreter.Parser.ParseRoll;
// helper functions // helper functions
Scope.roll = (Func<int, int>)Dungeoneer.Util.Roll; Scope.roll = (Func<int, int>)Dungeoneer.Util.Roll;
} }
/** <summary> Executes a Python file. </summary> */
public static void Run(string file) { Engine.ExecuteFile(file, Scope); } public static void Run(string file) { Engine.ExecuteFile(file, Scope); }
public static dynamic Expr(string expression) { return Engine.Execute(expression, Scope); }
/** <summary> used for the python interpreter repl. don't use this!!! </summary> */
public static dynamic Expression(string input) { return Engine.Execute(input, Scope); }
/** <summary>
* reimplementation of the Python builtin `input`.
* the default builtin doesn't work correctly.
* </summary>
*/
public static string Input(string prompt) { public static string Input(string prompt) {
Console.Write(prompt); Console.Write(prompt);
return Console.ReadLine(); return Console.ReadLine();
} }
/** <summary>
* implementation of the `interpret.input` helper object method. tokenizes
* the result of the `input` builtin.
* </summary>
*/
public static TokenSet LexInput(string prompt) {
var input = Input(prompt);
return Interpreter.Lexer.Tokenize(input);
}
} }
} }