Implement If blocks in scripts

This commit is contained in:
Yoshi Askharoun
2025-07-18 14:04:06 -05:00
parent 15b050a343
commit 044615088d
6 changed files with 199 additions and 24 deletions
+50
View File
@@ -0,0 +1,50 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Microsoft.Iris.DecompXml;
internal class CodeBlockInfo
{
public CodeBlockInfo(uint startOffset, uint endOffset, SyntaxKind kind, ExpressionSyntax expression = null)
{
StartOffset = startOffset;
EndOffset = endOffset;
Kind = kind;
Expression = expression;
}
public uint StartOffset { get; init; }
public uint EndOffset { get; init; }
public SyntaxKind Kind { get; init; }
public ExpressionSyntax Expression { get; init; }
public List<StatementSyntax> Statements { get; } = [];
public void FinalizeBlock(CodeBlockInfo parentBlock)
{
var blockBody = Block(Statements);
switch (Kind)
{
case SyntaxKind.IfStatement:
var ifStatement = IfStatement(Expression, blockBody);
parentBlock.Statements.Add(ifStatement);
break;
case SyntaxKind.ElseClause:
var elseClause = ElseClause(blockBody);
var ifElseBlock = (IfStatementSyntax)parentBlock.Statements[^1];
parentBlock.Statements[^1] = ifElseBlock.WithElse(elseClause);
break;
default:
throw new System.NotImplementedException($"Unrecognized code block kind '{Kind}'");
}
}
}
+7 -7
View File
@@ -78,14 +78,14 @@ internal class DecompileContext
public PropertySchema GetImportedProperty(Operand op) => ImportTables.PropertyImports[(ushort)op.Value];
public Instruction[] GetMethodBody(uint startOffset)
public IEnumerable<Instruction> GetMethodBody(uint startOffset)
{
return Instructions
.SkipWhile(i => i.Offset < startOffset)
.OrderBy(i => i.Offset)
.TakeWhile(i => i.OpCode is not (OpCode.ReturnValue or OpCode.ReturnVoid))
.OrderBy(i => i.Offset)
.ToArray();
foreach (var instruction in Instructions.SkipWhile(i => i.Offset < startOffset))
{
yield return instruction;
if (instruction.OpCode is OpCode.ReturnValue or OpCode.ReturnVoid)
yield break;
}
}
public IEnumerable<KeyValuePair<string, XNamespace>> GetUsedNamespaces()
+136 -14
View File
@@ -18,16 +18,36 @@ partial class Decompiler
{
private SyntaxTree DecompileScript(uint startOffset, MarkupTypeSchema export)
{
var methodBody = _context.GetMethodBody(startOffset);
var methodBody = _context.GetMethodBody(startOffset).ToArray();
Stack<BlockSyntax> blockStack = [];
blockStack.Push(Block());
Stack<CodeBlockInfo> blockStack = [];
blockStack.Push(new(0, methodBody[^1].Offset, SyntaxKind.Block, null));
Stack<object> stack = new();
for (int i = 0; i < methodBody.Length; i++)
{
var instruction = methodBody[i];
// TODO: Handle for loops
if (instruction.Offset == blockStack.Peek().EndOffset)
{
// Make sure there is only one top-level block
if (blockStack.Count > 1)
{
var currentBlock = blockStack.Pop();
currentBlock.FinalizeBlock(blockStack.Peek());
}
else
{
// End of function
if (i + 1 != methodBody.Length)
throw new InvalidOperationException("Expected end of function!");
break;
}
}
var opCode = instruction.OpCode;
try
@@ -73,7 +93,7 @@ partial class Decompiler
IrisExpression.ToSyntax(newSymbolValue, _context)
);
AddStatementToBlock(blockStack, ExpressionStatement(symbolAssignmentExpr));
blockStack.Peek().Statements.Add(ExpressionStatement(symbolAssignmentExpr));
break;
case OpCode.MethodInvoke:
@@ -116,7 +136,7 @@ partial class Decompiler
}
else
{
AddStatementToBlock(blockStack, ExpressionStatement(methodResult));
blockStack.Peek().Statements.Add(ExpressionStatement(methodResult));
}
if (pushLastParam)
@@ -130,7 +150,7 @@ partial class Decompiler
case OpCode.PropertyGetStatic:
var propToGet = _context.GetImportedProperty(instruction.Operands.First());
var propTarget = instruction.OpCode switch
var propGetTarget = instruction.OpCode switch
{
OpCode.PropertyGet => stack.Pop(),
OpCode.PropertyGetPeek => stack.Peek(),
@@ -138,26 +158,36 @@ partial class Decompiler
};
var propertyGetExpression = MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IrisExpression.ToSyntax(propTarget, _context),
IrisExpression.ToSyntax(propGetTarget, _context),
IdentifierName(propToGet.Name)
);
stack.Push(propertyGetExpression);
break;
case OpCode.PropertyInitialize:
var propertyToInit = _context.GetImportedProperty(instruction.Operands.First());
var newPropValue = stack.Pop();
case OpCode.PropertyAssign:
case OpCode.PropertyAssignStatic:
var propToSet = _context.GetImportedProperty(instruction.Operands.First());
var target = stack.Pop();
var xTarget = (XElement)ToXmlFriendlyObject(target);
var propSetTarget = opCode is OpCode.PropertyAssignStatic
? propToSet.Owner
: stack.Pop();
PropertyAssignOnXElement(xTarget, propertyToInit, IrisObject.Create(newPropValue, propertyToInit.PropertyType, _context));
var newPropValue = IrisExpression.ToSyntax(stack.Peek(), _context);
stack.Push(new IrisObject(xTarget, propertyToInit.Owner));
var propertySetExpression = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression,
IrisExpression.ToSyntax(propSetTarget, _context),
IdentifierName(propToSet.Name)
),
newPropValue
);
blockStack.Peek().Statements.Add(ExpressionStatement(propertySetExpression));
break;
case OpCode.PropertyDictionaryAdd:
// TODO
var targetDictProperty = _context.GetImportedProperty(instruction.Operands.ElementAt(0));
var keyReference = instruction.Operands.ElementAt(1);
@@ -171,12 +201,74 @@ partial class Decompiler
break;
case OpCode.PropertyListAdd:
// TODO
var targetListProperty = _context.GetImportedProperty(instruction.Operands.First());
var valueToAdd = stack.Pop();
var targetInstance2 = (XElement)ToXmlFriendlyObject(stack.Peek());
PropertyListAddOnXElement(targetInstance2, targetListProperty, IrisObject.Create(valueToAdd, null, _context));
break;
case OpCode.JumpIfFalse:
case OpCode.JumpIfFalsePeek:
case OpCode.JumpIfTruePeek:
var jumpToOffset = (uint)instruction.Operands.First().Value;
// TODO: What about for loops?
if (instruction.Offset > jumpToOffset)
{
throw new NotImplementedException();
}
var isPeek = opCode is OpCode.JumpIfFalsePeek or OpCode.JumpIfTruePeek or OpCode.JumpIfNullPeek;
var rawJumpCondition = IrisExpression.ToSyntax(isPeek ? stack.Peek() : stack.Pop(), _context);
var jumpCondition = opCode switch
{
OpCode.JumpIfFalse or
OpCode.JumpIfFalsePeek => PrefixUnaryExpression(SyntaxKind.LogicalNotExpression, rawJumpCondition),
OpCode.JumpIfTruePeek => rawJumpCondition,
OpCode.JumpIfNullPeek => BinaryExpression(SyntaxKind.EqualsExpression,
rawJumpCondition,
LiteralExpression(SyntaxKind.NullLiteralExpression)),
_ => throw new NotImplementedException()
};
var ifBlock = new CodeBlockInfo(instruction.Offset, jumpToOffset, SyntaxKind.IfStatement, jumpCondition);
blockStack.Push(ifBlock);
break;
case OpCode.Operation:
var opHost = _context.GetImportedType(instruction.Operands.ElementAt(0));
var op = (OperationType)(int)(byte)instruction.Operands.ElementAt(1).Value;
var isUnary = TypeSchema.IsUnaryOperation(op);
var opSyntax = OperationToSyntaxKind(op);
ExpressionSyntax operationExpr;
if (isUnary)
{
var left = IrisExpression.ToSyntax(stack.Pop(), _context);
var isPostfix = op is OperationType.PostIncrement or OperationType.PostDecrement;
operationExpr = isPostfix
? PostfixUnaryExpression(opSyntax, left)
: PrefixUnaryExpression(opSyntax, left);
}
else
{
var right = IrisExpression.ToSyntax(stack.Pop(), _context);
var left = IrisExpression.ToSyntax(stack.Pop(), _context);
operationExpr = BinaryExpression(opSyntax, left, right);
}
stack.Push(operationExpr);
break;
}
}
catch (Exception ex)
@@ -216,4 +308,34 @@ partial class Decompiler
block = block.AddStatements(statement);
blockStack.Push(block);
}
private static SyntaxKind OperationToSyntaxKind(OperationType operation)
{
return operation switch
{
OperationType.MathAdd => SyntaxKind.AddExpression,
OperationType.MathSubtract => SyntaxKind.SubtractExpression,
OperationType.MathMultiply => SyntaxKind.MultiplyExpression,
OperationType.MathDivide => SyntaxKind.DivideExpression,
OperationType.MathModulus => SyntaxKind.ModuloExpression,
OperationType.MathNegate => SyntaxKind.UnaryMinusExpression,
OperationType.LogicalAnd => SyntaxKind.LogicalAndExpression,
OperationType.LogicalOr => SyntaxKind.LogicalOrExpression,
OperationType.LogicalNot => SyntaxKind.LogicalNotExpression,
OperationType.RelationalEquals => SyntaxKind.EqualsExpression,
OperationType.RelationalNotEquals => SyntaxKind.NotEqualsExpression,
OperationType.RelationalLessThan => SyntaxKind.LessThanExpression,
OperationType.RelationalGreaterThan => SyntaxKind.GreaterThanExpression,
OperationType.RelationalLessThanEquals => SyntaxKind.LessThanOrEqualExpression,
OperationType.RelationalGreaterThanEquals => SyntaxKind.GreaterThanOrEqualExpression,
OperationType.RelationalIs => SyntaxKind.IsExpression,
OperationType.PostIncrement => SyntaxKind.PostIncrementExpression,
OperationType.PostDecrement => SyntaxKind.PostDecrementExpression,
_ => throw new ArgumentException($"Invalid operation type '{operation}'", nameof(operation))
};
}
}
+1 -1
View File
@@ -105,7 +105,7 @@ public partial class Decompiler
private Stack<object> AnalyzeMethodForInit(uint startOffset, XElement elemToInit, MarkupTypeSchema initType, string methodName = "")
{
var methodBody = _context.GetMethodBody(startOffset);
var methodBody = _context.GetMethodBody(startOffset).ToArray();
Stack<object> stack = new([elemToInit]);
@@ -2,7 +2,6 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.Iris.Asm;
using Microsoft.Iris.Markup;
using Newtonsoft.Json.Linq;
using System;
using System.Linq.Expressions;