Compare commits

..

No commits in common. "main" and "real-lexing" have entirely different histories.

7 changed files with 101 additions and 110 deletions

View file

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

View file

@ -76,19 +76,11 @@ 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)
@ -104,39 +96,18 @@ namespace Dungeoneer.Interpreter {
}
/** <summary> a token that contains a value. </summary> */
public abstract class DataToken<T> : Token {
public abstract class MetadataToken<T> : Token {
public T Value { get; protected set; }
}
/** <summary> a token that is ignored during arithmetic evaluation. </summary> */
public abstract class MetadataToken<T> : DataToken<T> {
public T Value { get; protected set; }
}
/** <summary> a token with a numerical value </summary> */
public abstract class ValueToken : DataToken<int> {
public abstract class ValueToken : Token {
public int Value { get; protected set; }
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 class OperatorToken : Token {
public char Value;
public OperatorToken(char op) { Value = op; }
@ -144,7 +115,6 @@ namespace Dungeoneer.Interpreter {
}
/** <summary> a token representing a numeric literal </summary> */
public class NumberToken : ValueToken {
internal static readonly Regex Pattern = new Regex(@"^(\d+)$");
@ -155,7 +125,8 @@ namespace Dungeoneer.Interpreter {
}
public class NameToken : DataToken<string> {
public class NameToken : Token {
public string Value { get; private set; }
internal static readonly Regex Pattern = new Regex(@"^[A-Za-z][A-Za-z\d\-_]+$");

View file

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

View file

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

View file

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

View file

@ -1,44 +1,93 @@
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) {
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) {
/*
foreach(var roll in rolls)
if(roll > dc.Value) {
success = true;
break;
}
}
string message = success ? $"{Format.Green}pass" : $"{Format.Red}fail";
Console.WriteLine($"{dicePool} > {target}\n => {message}{Format.Reset}");
var result = RollResult.Wrap(success);
Console.WriteLine($"{dice}\n => {result}");*/
}
}

View file

@ -19,51 +19,36 @@ 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); // `input` in IP is broken; swap for our implementation
builtin.RemoveVariable("open"); // remove 'open' builtin; don't trust scripts with file i/o
builtin.SetVariable("input", (Func<string, string>)Input);
builtin.RemoveVariable("open");
// helper vars
Scope.repl_commands = Program.ReplCommands; // repl command dictionary
Scope.roll_macros = Program.RollMacros; // roll macro dictionary
Scope.repl_commands = Program.ReplCommands;
Scope.roll_macros = Program.RollMacros;
// 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, int>)Interpreter.Parser.ParseRoll;
// Scope.interpret.parse_roll = (Func<string, string>)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);
}
}
}