Compare commits
3 commits
real-lexin
...
main
Author | SHA1 | Date | |
---|---|---|---|
c8bf6073e6 | |||
1a23989a8f | |||
e8a8bdde85 |
7 changed files with 112 additions and 103 deletions
|
@ -9,6 +9,14 @@ namespace Dungeoneer.Error {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class SyntaxException : Exception {
|
||||||
|
|
||||||
|
public SyntaxException(string message)
|
||||||
|
: base(message)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
47
src/Lex.cs
47
src/Lex.cs
|
@ -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\-_]+$");
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
12
src/Parse.cs
12
src/Parse.cs
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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!");
|
||||||
}
|
}
|
||||||
|
|
105
src/Roll.cs
105
src/Roll.cs
|
@ -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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue