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 TokenSet() : base() { }
|
||||
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() {
|
||||
var output = "";
|
||||
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 abstract class ValueToken : Token {
|
||||
public int Value { get; protected set; }
|
||||
|
||||
public override string ToString() { return Value.ToString(); }
|
||||
/** <summary> a token that is ignored during arithmetic evaluation. </summary> */
|
||||
public abstract class MetadataToken<T> : DataToken<T> {
|
||||
public T Value { get; protected set; }
|
||||
}
|
||||
|
||||
public class OperatorToken : Token {
|
||||
public char Value;
|
||||
/** <summary> a token with a numerical value </summary> */
|
||||
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; }
|
||||
|
||||
|
@ -115,6 +144,7 @@ namespace Dungeoneer.Interpreter {
|
|||
|
||||
}
|
||||
|
||||
/** <summary> a token representing a numeric literal </summary> */
|
||||
public class NumberToken : ValueToken {
|
||||
|
||||
internal static readonly Regex Pattern = new Regex(@"^(\d+)$");
|
||||
|
@ -125,8 +155,7 @@ namespace Dungeoneer.Interpreter {
|
|||
|
||||
}
|
||||
|
||||
public class NameToken : Token {
|
||||
public string Value { get; private set; }
|
||||
public class NameToken : DataToken<string> {
|
||||
|
||||
internal static readonly Regex Pattern = new Regex(@"^[A-Za-z][A-Za-z\d\-_]+$");
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ using System.Dynamic;
|
|||
|
||||
namespace Dungeoneer.Objects {
|
||||
|
||||
|
||||
|
||||
public class Character {
|
||||
public string Name;
|
||||
|
||||
|
|
12
src/Parse.cs
12
src/Parse.cs
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
|
||||
using Dungeoneer.Error;
|
||||
|
||||
namespace Dungeoneer.Interpreter {
|
||||
|
||||
public static class Parser {
|
||||
|
@ -14,23 +16,23 @@ namespace Dungeoneer.Interpreter {
|
|||
switch(token) {
|
||||
case ValueToken value:
|
||||
if(wasValue)
|
||||
throw new Exception("value -> value");
|
||||
throw new SyntaxException("Value followed by value");
|
||||
result = Operate(result, value.Value, operation);
|
||||
wasValue = true;
|
||||
break;
|
||||
case OperatorToken op:
|
||||
if(!wasValue)
|
||||
throw new Exception("operator -> operator");
|
||||
throw new SyntaxException("Operator followed by operator");
|
||||
operation = op.Value;
|
||||
wasValue = false;
|
||||
break;
|
||||
case DcToken dc:
|
||||
if(rollDc.HasValue)
|
||||
throw new Exception("two DC elements");
|
||||
throw new SyntaxException("Duplicate element: DC");
|
||||
rollDc = dc.Value;
|
||||
break;
|
||||
default:
|
||||
throw new Exception($"invalid token: {token.GetType()}");
|
||||
throw new SyntaxException($"invalid token: {token.GetType()}");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -44,7 +46,7 @@ namespace Dungeoneer.Interpreter {
|
|||
case '/': return (a / b);
|
||||
case '%': return (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);
|
||||
if(first is NameToken) {
|
||||
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 {
|
||||
Console.WriteLine($"no command '{raw}' found!");
|
||||
}
|
||||
|
|
105
src/Roll.cs
105
src/Roll.cs
|
@ -1,93 +1,44 @@
|
|||
using System.Security.Cryptography;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Dungeoneer.Error;
|
||||
using Dungeoneer.Interpreter;
|
||||
|
||||
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 void Pool(TokenSet input) {
|
||||
/*
|
||||
foreach(var roll in rolls)
|
||||
if(roll > dc.Value) {
|
||||
if(input.Count != 2)
|
||||
throw new SyntaxException($"expected 2 arguments ({input.Count} given)");
|
||||
|
||||
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;
|
||||
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();
|
||||
dynamic builtin = Engine.GetBuiltinModule();
|
||||
|
||||
// set up imports
|
||||
var paths = Engine.GetSearchPaths();
|
||||
paths.Add("/usr/lib/python3.12/");
|
||||
Engine.SetSearchPaths(paths);
|
||||
|
||||
// set up python environment
|
||||
builtin.SetVariable("input", (Func<string, string>)Input);
|
||||
builtin.RemoveVariable("open");
|
||||
builtin.SetVariable("input", (Func<string, string>)Input); // `input` in IP is broken; swap for our implementation
|
||||
builtin.RemoveVariable("open"); // remove 'open' builtin; don't trust scripts with file i/o
|
||||
|
||||
// helper vars
|
||||
Scope.repl_commands = Program.ReplCommands;
|
||||
Scope.roll_macros = Program.RollMacros;
|
||||
Scope.repl_commands = Program.ReplCommands; // repl command dictionary
|
||||
Scope.roll_macros = Program.RollMacros; // roll macro dictionary
|
||||
|
||||
// interpreter api object
|
||||
Scope.interpret = new ExpandoObject();
|
||||
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.parse_roll = (Func<string, string>)Interpreter.Parser.ParseRoll;
|
||||
Scope.interpret.parse_roll = (Func<string, int>)Interpreter.Parser.ParseRoll;
|
||||
|
||||
// helper functions
|
||||
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 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) {
|
||||
Console.Write(prompt);
|
||||
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