From f4a4e0bb08f59cd80c82b626db949b5e6e541064 Mon Sep 17 00:00:00 2001 From: homothetyhk Date: Sat, 3 Feb 2024 01:51:51 -0800 Subject: [PATCH] Add method to recover expression from string item effect. --- RandomizerCore/StringItems/StringItem.cs | 73 +++++++++++++++++++ .../StringParsing/ExpressionBuilder.cs | 5 ++ .../EffectToExpressionTests.cs | 55 ++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 RandomizerCoreTests/EffectToExpressionTests.cs diff --git a/RandomizerCore/StringItems/StringItem.cs b/RandomizerCore/StringItems/StringItem.cs index 2b2fe7b..22abf1e 100644 --- a/RandomizerCore/StringItems/StringItem.cs +++ b/RandomizerCore/StringItems/StringItem.cs @@ -1,5 +1,6 @@ using RandomizerCore.Logic; using RandomizerCore.LogicItems; +using RandomizerCore.StringParsing; namespace RandomizerCore.StringItems { @@ -39,6 +40,17 @@ public abstract record StringItemEffect public abstract bool AddTo(ProgressionManager pm); public abstract bool CheckForEffect(ProgressionManager pm); public abstract IEnumerable GetAffectedTerms(); + /// + /// Converts the effect to an expression tree, with standardized formatting. May differ from the expression implied by . + /// + public abstract IExpression ToExpression(); + /// + /// Converts the effect to an effect string. By default, this prints the result of , and may differ from . + /// + public virtual string ToEffectString() => ToExpression().Print(); + internal static ItemExpressionFactory ExpressionFactory { get; } = new ItemExpressionFactory(); + internal static ItemOperatorProvider OperatorProvider { get; } = new ItemOperatorProvider(); + internal static ExpressionBuilder ExpressionBuilder { get; } = new(OperatorProvider, ExpressionFactory); } public sealed record EmptyEffect : StringItemEffect @@ -47,6 +59,7 @@ public sealed record EmptyEffect : StringItemEffect public override bool AddTo(ProgressionManager pm) => false; public override bool CheckForEffect(ProgressionManager pm) => false; public override IEnumerable GetAffectedTerms() => Enumerable.Empty(); + public override IExpression ToExpression() => ExpressionBuilder.NameAtom(ItemExpressionFactory.EmptyEffect); } public record AllOfEffect : StringItemEffect @@ -74,6 +87,21 @@ public override IEnumerable GetAffectedTerms() { return Effects.SelectMany(e => e.GetAffectedTerms()); } + + public override IExpression ToExpression() + { + return ExpressionBuilder.ApplyInfixOperatorLeftAssoc(Effects.Select(e => e.ToExpression()), ExpressionBuilder.Op(ItemOperatorProvider.Chaining)); + } + + public virtual bool Equals(AllOfEffect? other) + { + return other is not null && other.Effects.SequenceEqual(Effects); + } + + public override int GetHashCode() + { + return Effects[0].GetHashCode() ^ Effects.Length; + } } public record FirstOfEffect : StringItemEffect @@ -100,6 +128,22 @@ public override IEnumerable GetAffectedTerms() { return Effects.SelectMany(e => e.GetAffectedTerms()); } + + + public override IExpression ToExpression() + { + return ExpressionBuilder.ApplyInfixOperatorLeftAssoc(Effects.Select(e => e.ToExpression()), ExpressionBuilder.Op(ItemOperatorProvider.ShortCircuitChaining)); + } + + public virtual bool Equals(FirstOfEffect? other) + { + return other is not null && other.Effects.SequenceEqual(Effects); + } + + public override int GetHashCode() + { + return Effects[0].GetHashCode() ^ Effects.Length; + } } public record IncrementEffect(int Value, Term Term) : StringItemEffect @@ -119,6 +163,18 @@ public override IEnumerable GetAffectedTerms() { yield return Term; } + + public override IExpression ToExpression() + { + if (Value == 1) + { + return ExpressionBuilder.ApplyPostfixOperator(ExpressionBuilder.NameAtom(Term.Name), ExpressionBuilder.Op(ItemOperatorProvider.Increment)); + } + else + { + return ExpressionBuilder.ApplyInfixOperator(ExpressionBuilder.NameAtom(Term.Name), ExpressionBuilder.Op(ItemOperatorProvider.AdditionAssignment), ExpressionBuilder.NumberAtom(Value)); + } + } } public record MaxWithEffect(int Value, Term Term) : StringItemEffect @@ -142,6 +198,11 @@ public override IEnumerable GetAffectedTerms() { yield return Term; } + + public override IExpression ToExpression() + { + return ExpressionBuilder.ApplyInfixOperator(ExpressionBuilder.NameAtom(Term.Name), ExpressionBuilder.Op(ItemOperatorProvider.MaxAssignment), ExpressionBuilder.NumberAtom(Value)); + } } public record ConditionalEffect(LogicDef Logic, StringItemEffect Effect, bool Negated = false) : StringItemEffect @@ -164,6 +225,13 @@ public override IEnumerable GetAffectedTerms() { return Effect.GetAffectedTerms(); } + + public override IExpression ToExpression() + { + IExpression logicExpr = ExpressionBuilder.StringAtom(Logic.InfixSource); + if (Negated) logicExpr = ExpressionBuilder.ApplyPrefixOperator(ExpressionBuilder.Op(ItemOperatorProvider.Negation), logicExpr); + return ExpressionBuilder.ApplyInfixOperator(logicExpr, ExpressionBuilder.Op(ItemOperatorProvider.Conditional), Effect.ToExpression()); + } } public record ReferenceEffect(LogicItem Item) : StringItemEffect @@ -191,5 +259,10 @@ public override IEnumerable GetAffectedTerms() { return Item.GetAffectedTerms(); } + + public override IExpression ToExpression() + { + return ExpressionBuilder.ApplyPrefixOperator(ExpressionBuilder.Op(ItemOperatorProvider.Reference), ExpressionBuilder.NameAtom(Item.Name)); + } } } diff --git a/RandomizerCore/StringParsing/ExpressionBuilder.cs b/RandomizerCore/StringParsing/ExpressionBuilder.cs index 8298f66..f349df3 100644 --- a/RandomizerCore/StringParsing/ExpressionBuilder.cs +++ b/RandomizerCore/StringParsing/ExpressionBuilder.cs @@ -44,6 +44,11 @@ public IExpression ApplyInfixOperator(IExpression argL, OperatorToken op, return expressionFactory.CreateInfixExpression(argL, op, argR); } + public IExpression ApplyInfixOperatorLeftAssoc(IEnumerable> args, OperatorToken op) + { + return args.Aggregate((expr, operand) => ApplyInfixOperator(expr, op, operand)); + } + public IExpression ApplyPrefixOperator(OperatorToken op, IExpression operand) { int? pOperand = GetBindingPower(operand, true); diff --git a/RandomizerCoreTests/EffectToExpressionTests.cs b/RandomizerCoreTests/EffectToExpressionTests.cs new file mode 100644 index 0000000..062663a --- /dev/null +++ b/RandomizerCoreTests/EffectToExpressionTests.cs @@ -0,0 +1,55 @@ +using FluentAssertions; +using RandomizerCore.Logic; +using RandomizerCore.StringItems; +using RandomizerCore.StringParsing; + +namespace RandomizerCoreTests +{ + public class EffectToExpressionTests + { + [Theory] + [InlineData("_")] + [InlineData("A++")] + [InlineData("A++ >> B++")] + [InlineData("A++ >> B++ >> C++")] + [InlineData("A++ >> B++ >|> C++")] + [InlineData("`A<1` => B++")] + [InlineData("!`A<1` => B++")] + [InlineData("*I")] + public void IdentityTest(string infix) + { + LogicManagerBuilder lmb = new(); + string[] terms = ["A", "B", "C"]; + foreach (string s in terms) lmb.GetOrAddTerm(s); + lmb.AddItem(new StringItemTemplate("I", "_")); + + lmb.AddItem(new StringItemTemplate("Test_Item", infix)); + + LogicManager lm = new(lmb); + + StringItem item = (StringItem)lm.GetItemStrict("Test_Item"); + + item.Effect.ToEffectString().Should().Be(infix); + } + + [Theory] + [InlineData("A++ >> (B++ >> C++)", "A++ >> B++ >> C++")] + [InlineData("A += 1", "A++")] + [InlineData("A+=2", "A += 2")] + public void NonidentityTest(string infix, string result) + { + LogicManagerBuilder lmb = new(); + string[] terms = ["A", "B", "C"]; + foreach (string s in terms) lmb.GetOrAddTerm(s); + lmb.AddItem(new StringItemTemplate("I", "_")); + + lmb.AddItem(new StringItemTemplate("Test_Item", infix)); + + LogicManager lm = new(lmb); + + StringItem item = (StringItem)lm.GetItemStrict("Test_Item"); + + item.Effect.ToEffectString().Should().Be(result); + } + } +}