Skip to content

Commit

Permalink
Initial predefined doubles implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
colinator27 committed Jun 29, 2024
1 parent aa9822d commit 79d6eb1
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 3 deletions.
11 changes: 11 additions & 0 deletions Underanalyzer/Decompiler/AST/Nodes/DoubleNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ public DoubleNode(double value)

public IExpressionNode Clean(ASTCleaner cleaner)
{
if (cleaner.Context.Settings.TryGetPredefinedDouble(Value, out string predefined, out bool isMultiPart))
{
if (isMultiPart)
{
return new PredefinedDoubleMultiNode(predefined, Value);
}
else
{
return new PredefinedDoubleSingleNode(predefined, Value);
}
}
return this;
}

Expand Down
67 changes: 67 additions & 0 deletions Underanalyzer/Decompiler/AST/Nodes/PredefinedDoubleNode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Underanalyzer.Decompiler.GameSpecific;

namespace Underanalyzer.Decompiler.AST;

/// <summary>
/// Represents a predefined double constant in the AST, with one single part.
/// </summary>
public class PredefinedDoubleSingleNode : IExpressionNode, IConditionalValueNode
{
public string Value { get; }
public double OriginalValue { get; }

public bool Duplicated { get; set; } = false;
public bool Group { get; set; } = false;
public IGMInstruction.DataType StackType { get; set; } = IGMInstruction.DataType.Double;

public string ConditionalTypeName => "PredefinedDouble";
public string ConditionalValue => Value;

public PredefinedDoubleSingleNode(string value, double originalValue)
{
Value = value;
OriginalValue = originalValue;
}

public IExpressionNode Clean(ASTCleaner cleaner)
{
return this;
}

public virtual void Print(ASTPrinter printer)
{
printer.Write(Value);
}

public IExpressionNode ResolveMacroType(ASTCleaner cleaner, IMacroType type)
{
if (type is IMacroTypeConditional conditional)
{
return conditional.Resolve(cleaner, this);
}
return null;
}
}

/// <summary>
/// Represents a predefined double constant in the AST, with multiple parts.
/// </summary>
public class PredefinedDoubleMultiNode : PredefinedDoubleSingleNode, IMultiExpressionNode
{
public PredefinedDoubleMultiNode(string value, double originalValue) : base(value, originalValue)
{
}

public override void Print(ASTPrinter printer)
{
if (Group)
{
printer.Write('(');
}
base.Print(printer);
if (Group)
{
printer.Write(')');
}
}
}
58 changes: 55 additions & 3 deletions Underanalyzer/Decompiler/DecompileSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ This Source Code Form is subject to the terms of the Mozilla Public
file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

using System.Collections.Generic;

namespace Underanalyzer.Decompiler;

/// <summary>
/// Describes the necessary settings properties for the decompiler.
/// </summary>
public interface IDecompileSettings
{
// TODO: more settings :)


/// <summary>
/// String used to indent, e.g. tabs or some amount of spaces generally.
/// </summary>
Expand Down Expand Up @@ -132,6 +131,15 @@ public interface IDecompileSettings
/// If true, a warning is added to the decompile context.
/// </summary>
public bool AllowLeftoverDataOnStack { get; }

/// <summary>
/// Attempts to retrieve a predefined double value (such as <c>pi</c>), given its double form.
/// </summary>
/// <param name="value">The double as stored in the GML code</param>
/// <param name="result">The resulting value to be printed</param>
/// <param name="isResultMultiPart">True if parentheses may be needed around the value when printed (due to spaces or operations)</param>
/// <returns>True if a predefined double value is found; false otherwise.</returns>
public bool TryGetPredefinedDouble(double value, out string result, out bool isResultMultiPart);
}

/// <summary>
Expand Down Expand Up @@ -160,4 +168,48 @@ public class DecompileSettings : IDecompileSettings
public string UnknownEnumValuePattern { get; set; } = "Value_{0}";
public string UnknownArgumentNamePattern { get; set; } = "arg{0}";
public bool AllowLeftoverDataOnStack { get; set; } = false;

// Some basic data populated from code seen in the wild
// TODO: populate this with more values by default?
public Dictionary<double, string> SinglePartPredefinedDoubles = new()
{
{ 3.141592653589793, "pi" },
};
public Dictionary<double, string> MultiPartPredefinedDoubles = new()
{
{ 6.283185307179586, "2 * pi" },
{ 12.566370614359172, "4 * pi" },
{ 31.41592653589793, "10 * pi" },
{ 0.3333333333333333, "1/3" },
{ 0.6666666666666666, "2/3" },
{ 1.3333333333333333, "4/3" },
{ 23.333333333333332, "70/3" },
{ 73.33333333333333, "220/3" },
{ 206.66666666666666, "620/3" },
{ 51.42857142857143, "360/7" },
{ 1.0909090909090908, "12/11" },
{ 0.06666666666666667, "1/15" },
{ 0.9523809523809523, "20/21" },
{ 0.03333333333333333, "1/30" },
{ 0.008333333333333333, "1/120" }
};

public bool TryGetPredefinedDouble(double value, out string result, out bool isResultMultiPart)
{
if (SinglePartPredefinedDoubles.TryGetValue(value, out result))
{
isResultMultiPart = false;
return true;
}

if (MultiPartPredefinedDoubles.TryGetValue(value, out result))
{
isResultMultiPart = true;
return true;
}

result = null;
isResultMultiPart = false;
return false;
}
}
22 changes: 22 additions & 0 deletions UnderanalyzerTest/DecompileContext.DecompileToString.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1864,4 +1864,26 @@ function default_arg_color(arg0 = 1)
"""
);
}

[Fact]
public void TestPredefinedDoubles()
{
TestUtil.VerifyDecompileResult(
"""
push.d 3.141592653589793
pop.v.d self.a
push.d 6.283185307179586
push.v self.c
add.v.d
pop.v.v self.b
push.d 6.283185307179586
pop.v.v self.d
""",
"""
a = pi;
b = (2 * pi) + c;
d = 2 * pi;
"""
);
}
}

0 comments on commit 79d6eb1

Please sign in to comment.