diff --git a/UndertaleModLib/Compiler/AssemblyWriter.cs b/UndertaleModLib/Compiler/AssemblyWriter.cs index e1e0f9c65..389507d79 100644 --- a/UndertaleModLib/Compiler/AssemblyWriter.cs +++ b/UndertaleModLib/Compiler/AssemblyWriter.cs @@ -1064,6 +1064,17 @@ private static void AssembleStatement(CodeWriter cw, Parser.Statement s, int rem case Parser.Statement.StatementKind.Exit: AssembleExit(cw); break; + case Parser.Statement.StatementKind.ExprVariableRef: + for (int i = s.Children.Count - 1; i >= 1; i--) + { + if (s.Children[i].Kind == Parser.Statement.StatementKind.ExprFunctionCall) + { + PushFunctionArgs(cw, s.Children[i]); + } + } + AssembleVariablePush(cw, s); + cw.Emit(Opcode.Popz, cw.typeStack.Pop()); + break; default: AssemblyWriterError(cw, "Expected a statement, none found", s.Token); break; @@ -1158,7 +1169,7 @@ private static void AssembleExit(CodeWriter cw) cw.Emit(Opcode.Exit, DataType.Int32); } - private static void AssembleFunctionCall(CodeWriter cw, Parser.Statement fc) + private static void PushFunctionArgs(CodeWriter cw, Parser.Statement fc) { // Needs to push args onto stack backwards for (int i = fc.Children.Count - 1; i >= 0; i--) @@ -1172,13 +1183,42 @@ private static void AssembleFunctionCall(CodeWriter cw, Parser.Statement fc) cw.Emit(Opcode.Conv, typeToConvertFrom, DataType.Variable); } } + } + + private static void AssembleFunctionCall(CodeWriter cw, Parser.Statement fc, bool isVariableCall = false, bool isSelf = false) { + if (!isVariableCall) + { + PushFunctionArgs(cw, fc); + } - cw.funcPatches.Add(new FunctionPatch() + if (isVariableCall) { - Target = cw.EmitRef(Opcode.Call, DataType.Int32), - Name = fc.Text, - ArgCount = fc.Children.Count - }); + /* + dup.v 3 8 ;;; this is a weird GMS2.3+ swap instruction + dup.v 0 + push.v stacktop.on_room_start + callv.v 3 + */ + byte argCount = (byte)(fc.Children.Count); + + // this is still a total mess + int varId = cw.compileContext.GetAssetIndexByName(fc.Text); + Parser.Statement funcVar = new Parser.Statement(Parser.Statement.StatementKind.ExprSingleVariable); + funcVar.ID = varId; + funcVar.Text = fc.Text; + AssembleVariablePush(cw, funcVar, false, false, false, true); + + cw.Emit(Opcode.CallV, DataType.Variable, DataType.Variable).Extra = argCount; + } + else + { + cw.funcPatches.Add(new FunctionPatch() + { + Target = cw.EmitRef(Opcode.Call, DataType.Int32), + Name = fc.Text, + ArgCount = fc.Children.Count + }); + } cw.typeStack.Push(DataType.Variable); } @@ -1700,18 +1740,18 @@ private static void ConvertTypeForBinaryOp(CodeWriter cw, Lexer.Token.TokenKind } // Workaround for out parameters - private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, bool duplicate = false, bool useLongDupForArray = false, bool useNoSpecificType = false) + private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, bool duplicate = false, bool useLongDupForArray = false, bool useNoSpecificType = false, bool forceBuiltin = false) { - AssembleVariablePush(cw, e, out _, out _, duplicate, useLongDupForArray, useNoSpecificType); + AssembleVariablePush(cw, e, out _, out _, duplicate, useLongDupForArray, useNoSpecificType, forceBuiltin); } // Workaround for out parameters #2 - private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out bool isSingle, bool duplicate = false, bool useLongDupForArray = false, bool useNoSpecificType = false) + private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out bool isSingle, bool duplicate = false, bool useLongDupForArray = false, bool useNoSpecificType = false, bool forceBuiltin = false) { - AssembleVariablePush(cw, e, out isSingle, out _, duplicate, useLongDupForArray, useNoSpecificType); + AssembleVariablePush(cw, e, out isSingle, out _, duplicate, useLongDupForArray, useNoSpecificType, forceBuiltin); } - private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out bool isSingle, out bool isArray, bool duplicate = false, bool useLongDupForArray = false, bool useNoSpecificType = false) + private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out bool isSingle, out bool isArray, bool duplicate = false, bool useLongDupForArray = false, bool useNoSpecificType = false, bool forceBuiltin = false) { isSingle = false; isArray = false; @@ -1770,7 +1810,7 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out { Target = cw.EmitRef(Opcode.Push, DataType.Variable), Name = e.Children[0].Text, - InstType = GetIDPrefixSpecial(e.Children[0].ID), + InstType = forceBuiltin ? InstanceType.Builtin : GetIDPrefixSpecial(e.Children[0].ID), VarType = VariableType.Array }); } @@ -1822,7 +1862,7 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out { Target = cw.EmitRef(Opcode.Push, DataType.Variable), Name = name, - InstType = InstanceType.Self, + InstType = forceBuiltin ? InstanceType.Builtin : InstanceType.Self, VarType = VariableType.Normal }); } @@ -1850,7 +1890,7 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out { Target = cw.EmitRef(Opcode.Push, DataType.Variable), Name = name, - InstType = (InstanceType)id, + InstType = forceBuiltin ? InstanceType.Builtin : (InstanceType)id, VarType = VariableType.Normal }); break; @@ -1862,8 +1902,11 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out AssembleExpression(cw, e.Children[0]); if (CompileContext.GMS2_3 && cw.typeStack.Peek() == DataType.Variable) { - cw.typeStack.Pop(); - cw.Emit(Opcode.PushI, DataType.Int16).Value = (short)-9; // stacktop conversion + if (e.Children.Count < 1 || e.Children[1].Kind != Parser.Statement.StatementKind.ExprFunctionCall) + { + cw.typeStack.Pop(); + cw.Emit(Opcode.PushI, DataType.Int16).Value = (short)-9; // stacktop conversion + } } else if (cw.typeStack.Peek() != DataType.Int32) // apparently it converts to ints { @@ -1872,8 +1915,16 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out for (int next = 1; next < e.Children.Count; next++) { - if (e.Children[next].Children.Count != 0) + if (e.Children[next].Children.Count != 0 || e.Children[next].Kind == Parser.Statement.StatementKind.ExprFunctionCall) { + if (e.Children[next].Kind == Parser.Statement.StatementKind.ExprFunctionCall) + { + // Function call + AssembleFunctionCall(cw, e.Children[next], true, false); + cw.typeStack.Push(DataType.Variable); + continue; + } + AssembleArrayPush(cw, e.Children[next]); bool notLast = (next + 1 < e.Children.Count); if (!notLast && duplicate) // ha ha, double negatives @@ -1887,7 +1938,7 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out { Target = cw.EmitRef(Opcode.Push, DataType.Variable), Name = e.Children[next].Text, - InstType = GetIDPrefixSpecial(e.Children[next].ID), + InstType = forceBuiltin ? InstanceType.Builtin : GetIDPrefixSpecial(e.Children[next].ID), VarType = VariableType.Array }); cw.typeStack.Push(DataType.Variable); @@ -1938,7 +1989,7 @@ private static void AssembleVariablePush(CodeWriter cw, Parser.Statement e, out } } fix.Children.Add(fix2); - AssembleVariablePush(cw, fix, out isSingle, out isArray, duplicate, useLongDupForArray, useNoSpecificType); + AssembleVariablePush(cw, fix, out isSingle, out isArray, duplicate, useLongDupForArray, useNoSpecificType, forceBuiltin); } else { @@ -2198,10 +2249,16 @@ private static void AssembleStoreVariable(CodeWriter cw, Parser.Statement s, Dat else if (s.Kind == Parser.Statement.StatementKind.ExprFuncName) { // Until further notice, I'm assuming this only comes up in 2.3 script definition. + + // sub funcs in an object store the self variable without the object function name suffix + string funcName = s.Text; + if (funcName.Contains("_gml_Object_")) + funcName = funcName[..funcName.LastIndexOf("_gml_Object_")]; + cw.varPatches.Add(new VariablePatch() { Target = cw.EmitRef(Opcode.Pop, DataType.Variable, DataType.Variable), - Name = s.Text, + Name = funcName, InstType = InstanceType.Self, VarType = VariableType.StackTop }); diff --git a/UndertaleModLib/Compiler/Lexer.cs b/UndertaleModLib/Compiler/Lexer.cs index 73443783f..579ca68f4 100644 --- a/UndertaleModLib/Compiler/Lexer.cs +++ b/UndertaleModLib/Compiler/Lexer.cs @@ -506,6 +506,9 @@ private static Token ReadIdentifier(CodeReader cr) "then" => new Token(Token.TokenKind.KeywordThen, cr.GetPositionInfo(index)), "mod" => new Token(Token.TokenKind.Mod, cr.GetPositionInfo(index)), "div" => new Token(Token.TokenKind.Div, cr.GetPositionInfo(index)), + // In GMS2.3, these keywords are special function calls instead of constants + "self" when CompileContext.GMS2_3 => new Token(Token.TokenKind.KeywordSelf, cr.GetPositionInfo(index)), + "other" when CompileContext.GMS2_3 => new Token(Token.TokenKind.KeywordOther, cr.GetPositionInfo(index)), _ => new Token(Token.TokenKind.Identifier, identifierText, cr.GetPositionInfo(index)), }; } @@ -813,6 +816,8 @@ public enum TokenKind KeywordContinue, KeywordStruct, // Apparently this exists KeywordFunction, + KeywordSelf, + KeywordOther, OpenBlock, // { CloseBlock, // } OpenArray, // [ diff --git a/UndertaleModLib/Compiler/Parser.cs b/UndertaleModLib/Compiler/Parser.cs index 9638cc1de..b92e4fc9a 100644 --- a/UndertaleModLib/Compiler/Parser.cs +++ b/UndertaleModLib/Compiler/Parser.cs @@ -704,6 +704,21 @@ private static Statement ParseFunction(CompileContext context) } } + // GMS2.3 instance keywords + private static Statement ParseInstanceKeyword(CompileContext context) + { + Lexer.Token token = remainingStageOne.Dequeue().Token; + Statement result = new Statement(Statement.StatementKind.ExprFunctionCall, token); + + // They literally convert into function calls + if (token.Kind == TokenKind.KeywordSelf) + result.Text = "@@This@@"; + else + result.Text = "@@Other@@"; + + return result; + } + private static Statement ParseFor(CompileContext context) { Statement result = new Statement(Statement.StatementKind.ForLoop, EnsureTokenKind(TokenKind.KeywordFor).Token); @@ -751,65 +766,77 @@ private static Statement ParseFor(CompileContext context) return result; } + private static Statement ParseAssignInner(CompileContext context, Statement left) + { + if (left.Kind != Statement.StatementKind.Pre && left.Kind != Statement.StatementKind.Post) + { + // hack because I don't know what I'm doing + string name; + if (left.Children.Count == 0 || left.Kind == Statement.StatementKind.ExprSingleVariable) + name = left.Text; + else + name = left.Children[left.Children.Count - 1]?.Text; + if (name == null) + return null; + + VariableInfo vi; + if ((context.BuiltInList.GlobalNotArray.TryGetValue(name, out vi) || + context.BuiltInList.GlobalArray.TryGetValue(name, out vi) || + context.BuiltInList.Instance.TryGetValue(name, out vi) || + context.BuiltInList.InstanceLimitedEvent.TryGetValue(name, out vi) + ) && !vi.CanSet) + { + ReportCodeError("Attempt to set a read-only variable.", left.Token, false); + } + + if (remainingStageOne.Count == 0) + { + ReportCodeError("Malformed assignment statement.", true); + return null; + } + Statement assign = new Statement(Statement.StatementKind.Assign, remainingStageOne.Dequeue().Token); + assign.Children.Add(left); + + if (assign.Token.Kind.In( + TokenKind.Assign, + TokenKind.AssignAnd, + TokenKind.AssignDivide, + TokenKind.AssignMinus, + TokenKind.AssignMod, + TokenKind.AssignOr, + TokenKind.AssignPlus, + TokenKind.AssignTimes, + TokenKind.AssignXor + )) + { + assign.Children.Add(new Statement(Statement.StatementKind.Token, assign.Token)); + assign.Children.Add(ParseExpression(context)); + } + else + { + ReportCodeError("Expected assignment operator.", assign.Token, true); + } + + return assign; + } + else + { + return left; + } + } + private static Statement ParseAssign(CompileContext context) { Statement left = ParsePostAndRef(context); if (left != null) { - if (left.Kind != Statement.StatementKind.Pre && left.Kind != Statement.StatementKind.Post) + if (left.Children.Count > 0 && left.Children.Last().Kind == Statement.StatementKind.ExprFunctionCall) { - // hack because I don't know what I'm doing - string name; - if (left.Children.Count == 0 || left.Kind == Statement.StatementKind.ExprSingleVariable) - name = left.Text; - else - name = left.Children[left.Children.Count - 1]?.Text; - if (name == null) - return null; - - VariableInfo vi; - if ((context.BuiltInList.GlobalNotArray.TryGetValue(name, out vi) || - context.BuiltInList.GlobalArray.TryGetValue(name, out vi) || - context.BuiltInList.Instance.TryGetValue(name, out vi) || - context.BuiltInList.InstanceLimitedEvent.TryGetValue(name, out vi) - ) && !vi.CanSet) - { - ReportCodeError("Attempt to set a read-only variable.", left.Token, false); - } - - if (remainingStageOne.Count == 0) - { - ReportCodeError("Malformed assignment statement.", true); - return null; - } - Statement assign = new Statement(Statement.StatementKind.Assign, remainingStageOne.Dequeue().Token); - assign.Children.Add(left); - - if (assign.Token.Kind.In( - TokenKind.Assign, - TokenKind.AssignAnd, - TokenKind.AssignDivide, - TokenKind.AssignMinus, - TokenKind.AssignMod, - TokenKind.AssignOr, - TokenKind.AssignPlus, - TokenKind.AssignTimes, - TokenKind.AssignXor - )) - { - assign.Children.Add(new Statement(Statement.StatementKind.Token, assign.Token)); - assign.Children.Add(ParseExpression(context)); - } - else - { - ReportCodeError("Expected assignment operator.", assign.Token, true); - } - - return assign; + return left; } else { - return left; + return ParseAssignInner(context, left); } } else @@ -1014,17 +1041,7 @@ private static Statement ParseGlobalVarDeclare(CompileContext context) return null; } - private static Statement ParseFunctionCall(CompileContext context, bool expression = false) - { - Statement s = EnsureTokenKind(TokenKind.ProcFunction); - - // gml_pragma processing can be done here, however we don't need to do that yet really - - EnsureTokenKind(TokenKind.OpenParen); // this should be guaranteed - - Statement result = new Statement(expression ? Statement.StatementKind.ExprFunctionCall : - Statement.StatementKind.FunctionCall, s.Token); - + private static void ParseFunctionCallArgs(CompileContext context, Statement result, Statement s) { // Parse the parameters/arguments while (remainingStageOne.Count > 0 && !hasError && !IsNextToken(TokenKind.EOF) && !IsNextToken(TokenKind.CloseParen)) { @@ -1040,6 +1057,20 @@ private static Statement ParseFunctionCall(CompileContext context, bool expressi } } } + } + + private static Statement ParseFunctionCall(CompileContext context, bool expression = false) + { + Statement s = EnsureTokenKind(TokenKind.ProcFunction); + + // gml_pragma processing can be done here, however we don't need to do that yet really + + EnsureTokenKind(TokenKind.OpenParen); // this should be guaranteed + + Statement result = new Statement(expression ? Statement.StatementKind.ExprFunctionCall : + Statement.StatementKind.FunctionCall, s.Token); + + ParseFunctionCallArgs(context, result, s); if (EnsureTokenKind(TokenKind.CloseParen) == null) return null; @@ -1377,6 +1408,19 @@ private static Statement ParsePostAndRef(CompileContext context) private static Statement ParseSingleVar(CompileContext context) { + if (IsNextToken(TokenKind.ProcFunction)) + { + Statement procFunc = EnsureTokenKind(TokenKind.ProcFunction); + EnsureTokenKind(TokenKind.OpenParen); // this should be guaranteed + Statement funcCall = new Statement(Statement.StatementKind.ExprFunctionCall, procFunc.Token); + + ParseFunctionCallArgs(context, funcCall, procFunc); + + if (EnsureTokenKind(TokenKind.CloseParen) == null) return null; + + return funcCall; + } + Statement s = EnsureTokenKind(TokenKind.ProcVariable); if (s == null) return null; @@ -1479,6 +1523,9 @@ private static Statement ParseLowLevel(CompileContext context) return ParseFunctionCall(context, true); case TokenKind.KeywordFunction: return ParseFunction(context); + case TokenKind.KeywordSelf: + case TokenKind.KeywordOther: + return ParseInstanceKeyword(context); case TokenKind.ProcVariable: { Statement variableRef = ParseSingleVar(context); diff --git a/UndertaleModLib/Decompiler/Instructions/Decompiler.ExpressionConstant.cs b/UndertaleModLib/Decompiler/Instructions/Decompiler.ExpressionConstant.cs index 374c68271..e428d1fc9 100644 --- a/UndertaleModLib/Decompiler/Instructions/Decompiler.ExpressionConstant.cs +++ b/UndertaleModLib/Decompiler/Instructions/Decompiler.ExpressionConstant.cs @@ -98,8 +98,11 @@ public override string ToString(DecompileContext context) // Archie: If statements are inefficient! Use a switch jump table! if (AssetType == AssetIDType.GameObject && !(Value is Int64)) // When the value is Int64, an example value is 343434343434. It is unknown what it represents, but it's not an InstanceType. { - int? val = ConvertToInt(Value); - if (val != null && val < 0 && val >= -16) + int? val = ConvertToInt(Value); + // Instance types past -5 are builtin, local, stacktop, arg and static. + // None of them are constants in GML. + // In GMS2.3, `self` and `other` are function calls instead of being -1 and -2. + if (val != null && val < (DecompileContext.GMS2_3 ? -2 : 0) && val >= -5) return ((UndertaleInstruction.InstanceType)Value).ToString().ToLower(CultureInfo.InvariantCulture); } else switch (AssetType) // Need to put else because otherwise it gets terribly unoptimized with GameObject type