diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index 250ed587e0..ef305dcfe6 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -602,37 +602,30 @@ public void ProcessLoopAssignment(LValue lValue) { } } - public void ProcessStatementForList(DMExpression list, DMExpression outputVar, DMComplexValueType? dmTypes, DMASTProcBlockInner body) { + public void ProcessStatementForList(DMExpression list, DMExpression outputVar, DMComplexValueType? typeCheck, DMASTProcBlockInner body) { if (outputVar is not LValue lValue) { compiler.Emit(WarningCode.BadExpression, outputVar.Location, "Invalid output var"); - lValue = null; + lValue = new BadLValue(outputVar.Location); } - // Depending on the var's type and possibly a given "as [types]", an implicit istype() check is performed - DreamPath? implicitTypeCheck = null; - if (dmTypes == null) { - // No "as" means the var's type will be used - implicitTypeCheck = lValue?.Path; - } else if (dmTypes.Value.TypePath != null) { - // "as /datum" will perform a check for /datum - implicitTypeCheck = dmTypes.Value.TypePath; - } else if (!dmTypes.Value.IsAnything) { - // "as anything" performs no check. Other values are unimplemented. - compiler.UnimplementedWarning(outputVar.Location, - $"As type {dmTypes} in for loops is unimplemented. No type check will be performed."); + // Having no "as [types]" will use the var's type for the type filter + if (typeCheck == null && lValue.Path != null) { + typeCheck = lValue.Path; } + bool performingImplicitIsType = false; list.EmitPushValue(ExprContext); - if (implicitTypeCheck != null) { - if (compiler.DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) { + if (typeCheck?.TypePath is { } typeCheckPath) { // We have a specific type to filter for + if (compiler.DMObjectTree.TryGetTypeId(typeCheckPath, out var filterTypeId)) { // Create an enumerator that will do the implicit istype() for us - proc.CreateFilteredListEnumerator(filterTypeId, implicitTypeCheck.Value); + proc.CreateFilteredListEnumerator(filterTypeId, typeCheckPath); } else { compiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location, - $"Cannot filter enumeration by type {implicitTypeCheck.Value}, it does not exist"); + $"Cannot filter enumeration by type {typeCheckPath}, it does not exist"); proc.CreateListEnumerator(); } - } else { + } else { // Either no type filter or we're using the slower "as [types]" + performingImplicitIsType = !(typeCheck is null || typeCheck.Value.IsAnything); proc.CreateListEnumerator(); } @@ -643,8 +636,43 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D { proc.MarkLoopContinue(loopLabel); - if (lValue != null) { - ProcessLoopAssignment(lValue); + ProcessLoopAssignment(lValue); + + // "as mob|etc" will insert code equivalent to "if(!(istype(X, mob) || istype(X, etc))) continue;" + // It would be ideal if the type filtering could be done by the interpreter, like it does when the var has a type + // But the code currently isn't structured in a way that it could be done nicely + if (performingImplicitIsType) { + var afterTypeCheckIf = proc.NewLabelName(); + var afterTypeCheckExpr = proc.NewLabelName(); + + void CheckType(DMValueType type, DreamPath path, ref bool doOr) { + if (!typeCheck!.Value.Type.HasFlag(type)) + return; + if (!compiler.DMObjectTree.TryGetTypeId(path, out var typeId)) + return; + + if (doOr) + proc.BooleanOr(afterTypeCheckExpr); + doOr = true; + + lValue.EmitPushValue(ExprContext); + proc.PushType(typeId); + proc.IsType(); + } + + bool doOr = false; // Only insert BooleanOr after the first type + CheckType(DMValueType.Area, DreamPath.Area, ref doOr); + CheckType(DMValueType.Turf, DreamPath.Turf, ref doOr); + CheckType(DMValueType.Obj, DreamPath.Obj, ref doOr); + CheckType(DMValueType.Mob, DreamPath.Mob, ref doOr); + proc.AddLabel(afterTypeCheckExpr); + if (doOr) { + proc.Not(); + proc.JumpIfFalse(afterTypeCheckIf); + proc.Continue(); + } + + proc.AddLabel(afterTypeCheckIf); } ProcessBlockInner(body); diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 670442f16b..73600868a1 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -579,11 +579,6 @@ public void Pop() { WriteOpcode(DreamProcOpcode.Pop); } - public void PopReference(DMReference reference) { - WriteOpcode(DreamProcOpcode.PopReference); - WriteReference(reference, false); - } - public void BooleanOr(string endLabel) { WriteOpcode(DreamProcOpcode.BooleanOr); WriteLabel(endLabel); diff --git a/DMCompiler/DM/Expressions/LValue.cs b/DMCompiler/DM/Expressions/LValue.cs index d5fd05f468..afae2566f9 100644 --- a/DMCompiler/DM/Expressions/LValue.cs +++ b/DMCompiler/DM/Expressions/LValue.cs @@ -31,6 +31,19 @@ public virtual void EmitPushInitial(ExpressionContext ctx) { } } +/// +/// Used when there was an error regarding L-Values +/// +/// Emit an error code before creating! +internal sealed class BadLValue(Location location) : LValue(location, null) { + public override void EmitPushValue(ExpressionContext ctx) { + // It's normal to have this expression exist when there are errors in the code + // But in the runtime we say it's a compiler bug because the compiler should never have output it + ctx.Proc.PushString("Encountered a bad LValue (compiler bug!)"); + ctx.Proc.Throw(); + } +} + // global internal class Global(Location location) : LValue(location, null) { public override DMReference EmitReference(ExpressionContext ctx, string endLabel, diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 777574d9dc..9e9ef6afaa 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -89,7 +89,7 @@ public static ProcStatus CreateAssociativeList(DMProcState state) { return ProcStatus.Continue; } - private static IDreamValueEnumerator GetContentsEnumerator(DreamObjectTree objectTree, AtomManager atomManager, DreamValue value, TreeEntry? filterType) { + private static IDreamValueEnumerator GetContentsEnumerator(AtomManager atomManager, DreamValue value, TreeEntry? filterType) { if (!value.TryGetValueAsDreamList(out var list)) { if (value.TryGetValueAsDreamObject(out var dreamObject)) { if (dreamObject == null) @@ -121,7 +121,7 @@ private static IDreamValueEnumerator GetContentsEnumerator(DreamObjectTree objec public static ProcStatus CreateListEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); - var enumerator = GetContentsEnumerator(state.Proc.ObjectTree, state.Proc.AtomManager, state.Pop(), null); + var enumerator = GetContentsEnumerator(state.Proc.AtomManager, state.Pop(), null); state.Enumerators[enumeratorId] = enumerator; return ProcStatus.Continue; @@ -131,7 +131,7 @@ public static ProcStatus CreateFilteredListEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); var filterTypeId = state.ReadInt(); var filterType = state.Proc.ObjectTree.GetTreeEntry(filterTypeId); - var enumerator = GetContentsEnumerator(state.Proc.ObjectTree, state.Proc.AtomManager, state.Pop(), filterType); + var enumerator = GetContentsEnumerator(state.Proc.AtomManager, state.Pop(), filterType); state.Enumerators[enumeratorId] = enumerator; return ProcStatus.Continue; @@ -281,29 +281,21 @@ public static ProcStatus EnumerateNoAssign(DMProcState state) { /// /// Helper function of to handle text macros that are "suffix" (coming after the noun) pronouns /// + /// + /// + /// /// This should be in MALE,FEMALE,PLURAL,NEUTER order. - private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadOnlySpan interps, int prevInterpIndex, string[] pronouns) - { - DreamObject? dreamObject; + private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadOnlySpan interps, int prevInterpIndex, string[] pronouns) { if (prevInterpIndex == -1 || prevInterpIndex >= interps.Length) // We should probably be throwing here - { return; - } - interps[prevInterpIndex].TryGetValueAsDreamObject(out dreamObject); - if (dreamObject == null) - { + if (!interps[prevInterpIndex].TryGetValueAsDreamObject(out var dreamObject)) return; - } - bool hasGender = dreamObject.TryGetVariable("gender", out var objectGender); // NOTE: in DM, this has to be a native property. - if (!hasGender) - { + if (!dreamObject.TryGetVariable("gender", out var objectGender)) // NOTE: in DM, this has to be a native property. return; - } if (!objectGender.TryGetValueAsString(out var genderStr)) return; - switch(genderStr) - { + switch(genderStr) { case "male": formattedString.Append(pronouns[0]); return; @@ -319,18 +311,17 @@ private static void HandleSuffixPronoun(ref StringBuilder formattedString, ReadO default: return; } - } private static void ToRoman(ref StringBuilder formattedString, ReadOnlySpan interps, int nextInterpIndex, bool upperCase) { char[] arr; if(upperCase) { - arr = new char[] { 'M', 'D', 'C', 'L', 'X', 'V', 'I' }; + arr = new[] { 'M', 'D', 'C', 'L', 'X', 'V', 'I' }; } else { - arr = new char[] { 'm', 'd', 'c', 'l', 'x', 'v', 'i' }; + arr = new[] { 'm', 'd', 'c', 'l', 'x', 'v', 'i' }; } - int[] numArr = new int[] { 1000, 500, 100, 50, 10, 5, 1 }; + int[] numArr = new[] { 1000, 500, 100, 50, 10, 5, 1 }; if(!interps[nextInterpIndex].TryGetValueAsFloat(out float value)) { return; @@ -363,6 +354,7 @@ private static void ToRoman(ref StringBuilder formattedString, ReadOnlySpan(out var dreamObject)) { bool hasName = dreamObject.TryGetVariable("name", out var objectName); if (!hasName) continue; string nameStr = objectName.Stringify(); - if (!DreamObject.StringIsProper(nameStr)) - { + if (!DreamObject.StringIsProper(nameStr)) { formattedString.Append(formatType == StringFormatEncoder.FormatSuffix.UpperDefiniteArticle ? "The " : "the "); } } + continue; } case StringFormatEncoder.FormatSuffix.UpperIndefiniteArticle: @@ -477,40 +467,38 @@ public static ProcStatus FormatString(DMProcState state) { } //Suffix macros case StringFormatEncoder.FormatSuffix.UpperSubjectPronoun: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "He", "She", "They", "Tt" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "He", "She", "They", "Tt" }); break; case StringFormatEncoder.FormatSuffix.LowerSubjectPronoun: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "he", "she", "they", "it" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "he", "she", "they", "it" }); break; case StringFormatEncoder.FormatSuffix.UpperPossessiveAdjective: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "His", "Her", "Their", "Its" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "His", "Her", "Their", "Its" }); break; case StringFormatEncoder.FormatSuffix.LowerPossessiveAdjective: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "his", "her", "their", "its" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "his", "her", "their", "its" }); break; case StringFormatEncoder.FormatSuffix.ObjectPronoun: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "him", "her", "them", "it" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "him", "her", "them", "it" }); break; case StringFormatEncoder.FormatSuffix.ReflexivePronoun: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "himself", "herself", "themself", "itself" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "himself", "herself", "themself", "itself" }); break; case StringFormatEncoder.FormatSuffix.UpperPossessivePronoun: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "His", "Hers", "Theirs", "Its" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "His", "Hers", "Theirs", "Its" }); break; case StringFormatEncoder.FormatSuffix.LowerPossessivePronoun: - HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new string[] { "his", "hers", "theirs", "its" }); + HandleSuffixPronoun(ref formattedString, interps, prevInterpIndex, new[] { "his", "hers", "theirs", "its" }); break; case StringFormatEncoder.FormatSuffix.PluralSuffix: - if (interps[prevInterpIndex].TryGetValueAsFloat(out var pluralNumber) && pluralNumber == 1) - { + if (interps[prevInterpIndex].TryGetValueAsFloat(out var pluralNumber) && pluralNumber.Equals(1f)) continue; - } + formattedString.Append("s"); continue; case StringFormatEncoder.FormatSuffix.OrdinalIndicator: var interp = interps[prevInterpIndex]; if (interp.TryGetValueAsInteger(out var ordinalNumber)) { - // For some mystical reason byond converts \th to integers // This is slightly hacky but the only reliable way I know how to replace the number // Need to call stringy to make sure its the right length to cut @@ -539,7 +527,7 @@ public static ProcStatus FormatString(DMProcState state) { formattedString.Append("0th"); } } else if (interp.TryGetValueAsDreamObject(out var interpObj)) { - var typeStr = interpObj.ObjectDefinition.Type.ToString(); + var typeStr = interpObj.ObjectDefinition.Type; var lastIdx = formattedString.ToString().LastIndexOf(typeStr); if (lastIdx != -1) { // Can this even fail? formattedString.Remove(lastIdx, typeStr.Length); @@ -550,6 +538,7 @@ public static ProcStatus FormatString(DMProcState state) { // we support this behavior for some non-floats but not all, so just append 0th anyways for now formattedString.Append("0th"); } + continue; case StringFormatEncoder.FormatSuffix.LowerRoman: postPrefix = formatType; @@ -582,7 +571,7 @@ public static ProcStatus Initial(DMProcState state) { return ProcStatus.Continue; } - if (!key.TryGetValueAsString(out string property)) { + if (!key.TryGetValueAsString(out string? property)) { throw new Exception("Invalid var for initial() call: " + key); } @@ -707,9 +696,11 @@ public static ProcStatus PushGlobalVars(DMProcState state) { state.Push(new DreamValue(new DreamGlobalVars(state.Proc.ObjectTree.List.ObjectDefinition))); return ProcStatus.Continue; } + #endregion Values #region Math + public static ProcStatus Add(DMProcState state) { DreamValue second = state.Pop(); DreamValue first = state.Pop(); @@ -732,6 +723,7 @@ public static ProcStatus Add(DMProcState state) { if (second.Type == DreamValue.DreamValueType.Float) { output = new DreamValue(firstFloat + second.MustGetValueAsFloat()); } + break; } case DreamValue.DreamValueType.String when second.Type == DreamValue.DreamValueType.String: @@ -1048,6 +1040,7 @@ public static ProcStatus BooleanOr(DMProcState state) { state.Push(a); state.Jump(jumpPosition); } + return ProcStatus.Continue; } @@ -1217,6 +1210,7 @@ public static ProcStatus Multiply(DMProcState state) { } else { throw new Exception($"Invalid multiply operation on {first} and {second}"); } + return ProcStatus.Continue; } @@ -1310,9 +1304,11 @@ public static ProcStatus Subtract(DMProcState state) { return ProcStatus.Continue; } + #endregion Math #region Comparisons + public static ProcStatus CompareEquals(DMProcState state) { DreamValue second = state.Pop(); DreamValue first = state.Pop(); @@ -1457,6 +1453,7 @@ private static DreamValue TypecheckHelper(DreamValue typeValue, DreamValue value #endregion Comparisons #region Flow + public static ProcStatus Call(DMProcState state) { DreamReference procRef = state.ReadReference(); var argumentInfo = state.ReadProcArguments(); @@ -1865,9 +1862,11 @@ public static ProcStatus ReturnReferenceValue(DMProcState state) { state.SetReturn(state.GetReferenceValue(reference)); return ProcStatus.Returned; } + #endregion Flow #region Builtins + public static ProcStatus GetStep(DMProcState state) { var d = state.Pop(); var l = state.Pop(); @@ -2253,10 +2252,10 @@ public static ProcStatus PickUnweighted(DMProcState state) { } public static ProcStatus Prob(DMProcState state) { - DreamValue P = state.Pop(); + DreamValue probability = state.Pop(); - if (P.TryGetValueAsFloat(out float probability)) { - int result = (state.DreamManager.Random.Prob(probability / 100)) ? 1 : 0; + if (probability.TryGetValueAsFloat(out float probabilityValue)) { + int result = (state.DreamManager.Random.Prob(probabilityValue / 100)) ? 1 : 0; state.Push(new DreamValue(result)); } else { @@ -2276,7 +2275,7 @@ public static ProcStatus IsSaved(DMProcState state) { return ProcStatus.Continue; } - if (!key.TryGetValueAsString(out string property)) { + if (!key.TryGetValueAsString(out string? property)) { throw new Exception($"Invalid var for issaved() call: {key}"); } @@ -2304,9 +2303,11 @@ public static ProcStatus IsSaved(DMProcState state) { return ProcStatus.Continue; } + #endregion Builtins #region Others + private static void PerformOutput(DreamValue a, DreamValue b) { if (a.TryGetValueAsDreamResource(out var resource)) { resource.Output(b); @@ -2650,9 +2651,11 @@ public static ProcStatus DereferenceCall(DMProcState state) { return state.Call(proc, instance, arguments); } + #endregion Others #region Helpers + [SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")] private static bool IsEqual(DreamValue first, DreamValue second) { // null should only ever be equal to null @@ -2756,9 +2759,10 @@ private static bool IsGreaterThan(DreamValue first, DreamValue second) { default: { if (first.IsNull) { if (second.Type == DreamValue.DreamValueType.Float) return 0 > second.MustGetValueAsFloat(); - if (second.TryGetValueAsString(out var s)) return false; + if (second.TryGetValueAsString(out _)) return false; if (second.IsNull) return false; } + throw new Exception("Invalid greater than comparison on " + first + " and " + second); } } @@ -2778,6 +2782,7 @@ private static bool IsLessThan(DreamValue first, DreamValue second) { if (second.TryGetValueAsString(out var s)) return s != ""; if (second.IsNull) return false; } + throw new Exception("Invalid less than comparison between " + first + " and " + second); } } @@ -2841,6 +2846,7 @@ private static DreamValue ModulusModulusValues(DreamValue first, DreamValue seco fraction -= MathF.Truncate(fraction); return new DreamValue(fraction * secondFloat); } + throw new Exception("Invalid modulusmodulus operation on " + first + " and " + second); } @@ -2913,16 +2919,18 @@ private static DreamValue CalculateGradient(List gradientValues, Dre float normalized = (index - leftBound) / (rightBound - leftBound); // Cheap way to make sure the gradient works at the extremes (eg 1 and 0) - if (!left.HasValue || (right.HasValue && normalized == 1) || (right.HasValue && normalized == 0)) { + if (!left.HasValue || (right.HasValue && normalized.Equals(1f)) || (right.HasValue && normalized == 0)) { if (right?.AByte == 255) { - return new DreamValue(right?.ToHexNoAlpha().ToLower() ?? "#00000000"); + return new DreamValue(right.Value.ToHexNoAlpha().ToLower()); } + return new DreamValue(right?.ToHex().ToLower() ?? "#00000000"); } else if (!right.HasValue) { - if (left?.AByte == 255) { - return new DreamValue(left?.ToHexNoAlpha().ToLower() ?? "#00000000"); + if (left.Value.AByte == 255) { + return new DreamValue(left.Value.ToHexNoAlpha().ToLower()); } - return new DreamValue(left?.ToHex().ToLower() ?? "#00000000"); + + return new DreamValue(left.Value.ToHex().ToLower()); } else if (!left.HasValue && !right.HasValue) { throw new InvalidOperationException("Failed to find any colors"); } @@ -3027,7 +3035,6 @@ public static ProcStatus PushNStrings(DMProcState state) { state.Push(new DreamValue(str)); } - return ProcStatus.Continue; } @@ -3094,7 +3101,7 @@ public static ProcStatus SwitchOnFloat(DMProcState state) { int casePosition = state.ReadInt(); var test = state.Pop(); if (test.TryGetValueAsFloat(out var value)) { - if (testValue == value) { + if (testValue.Equals(value)) { state.Jump(casePosition); } else { state.Push(test); @@ -3123,7 +3130,6 @@ public static ProcStatus SwitchOnString(DMProcState state) { return ProcStatus.Continue; } - public static ProcStatus PushNOfStringFloat(DMProcState state) { int count = state.ReadInt(); diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index f0913cd35b..89e64a1b16 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -13,406 +13,408 @@ using OpenDreamShared.Dream; using Robust.Shared.Utility; -namespace OpenDreamRuntime.Procs { - public sealed class DMProc : DreamProc { - public readonly byte[] Bytecode; - - public readonly bool IsNullProc; - public IReadOnlyList LocalNames { get; } - public readonly List SourceInfo; - - public readonly AtomManager AtomManager; - public readonly DreamManager DreamManager; - public readonly ProcScheduler ProcScheduler; - public readonly IDreamMapManager DreamMapManager; - public readonly IDreamDebugManager DreamDebugManager; - public readonly DreamResourceManager DreamResourceManager; - public readonly DreamObjectTree ObjectTree; - public readonly ServerVerbSystem VerbSystem; - - private readonly int _maxStackSize; - - public DMProc(int id, TreeEntry owningType, ProcDefinitionJson json, string? name, DreamManager dreamManager, AtomManager atomManager, IDreamMapManager dreamMapManager, IDreamDebugManager dreamDebugManager, DreamResourceManager dreamResourceManager, DreamObjectTree objectTree, ProcScheduler procScheduler, ServerVerbSystem verbSystem) - : base(id, owningType, name ?? json.Name, null, json.Attributes, GetArgumentNames(json), GetArgumentTypes(json), json.VerbSrc, json.VerbName, json.VerbCategory, json.VerbDesc, json.Invisibility, json.IsVerb) { - Bytecode = json.Bytecode ?? []; - LocalNames = json.Locals ?? []; - SourceInfo = json.SourceInfo; - _maxStackSize = json.MaxStackSize; - IsNullProc = CheckIfNullProc(); - - AtomManager = atomManager; - DreamManager = dreamManager; - ProcScheduler = procScheduler; - DreamMapManager = dreamMapManager; - DreamDebugManager = dreamDebugManager; - DreamResourceManager = dreamResourceManager; - ObjectTree = objectTree; - VerbSystem = verbSystem; - } - - public (string Source, int Line) GetSourceAtOffset(int offset) { - SourceInfoJson current = SourceInfo[0]; - string source = ObjectTree.Strings[current.File!.Value]; +namespace OpenDreamRuntime.Procs; + +public sealed class DMProc : DreamProc { + public readonly byte[] Bytecode; + + public readonly bool IsNullProc; + public IReadOnlyList LocalNames { get; } + public readonly List SourceInfo; + + public readonly AtomManager AtomManager; + public readonly DreamManager DreamManager; + public readonly ProcScheduler ProcScheduler; + public readonly IDreamMapManager DreamMapManager; + public readonly IDreamDebugManager DreamDebugManager; + public readonly DreamResourceManager DreamResourceManager; + public readonly DreamObjectTree ObjectTree; + public readonly ServerVerbSystem VerbSystem; + + private readonly int _maxStackSize; + + public DMProc(int id, TreeEntry owningType, ProcDefinitionJson json, string? name, DreamManager dreamManager, AtomManager atomManager, IDreamMapManager dreamMapManager, IDreamDebugManager dreamDebugManager, DreamResourceManager dreamResourceManager, DreamObjectTree objectTree, ProcScheduler procScheduler, ServerVerbSystem verbSystem) + : base(id, owningType, name ?? json.Name, null, json.Attributes, GetArgumentNames(json), GetArgumentTypes(json), json.VerbSrc, json.VerbName, json.VerbCategory, json.VerbDesc, json.Invisibility, json.IsVerb) { + Bytecode = json.Bytecode ?? []; + LocalNames = json.Locals ?? []; + SourceInfo = json.SourceInfo; + _maxStackSize = json.MaxStackSize; + IsNullProc = CheckIfNullProc(); + + AtomManager = atomManager; + DreamManager = dreamManager; + ProcScheduler = procScheduler; + DreamMapManager = dreamMapManager; + DreamDebugManager = dreamDebugManager; + DreamResourceManager = dreamResourceManager; + ObjectTree = objectTree; + VerbSystem = verbSystem; + } - int i = 0; - do { - var next = SourceInfo[i++]; - if (next.Offset > offset) - break; + public (string Source, int Line) GetSourceAtOffset(int offset) { + SourceInfoJson current = SourceInfo[0]; + string source = ObjectTree.Strings[current.File!.Value]; - current = next; - if (current.File != null) - source = ObjectTree.Strings[current.File.Value]; - } while (i < SourceInfo.Count); + int i = 0; + do { + var next = SourceInfo[i++]; + if (next.Offset > offset) + break; - return (source, current.Line); - } + current = next; + if (current.File != null) + source = ObjectTree.Strings[current.File.Value]; + } while (i < SourceInfo.Count); - /// - /// Checks if the given bytecode offset is the first on a line of the source code - /// - public bool IsOnLineChange(int offset) { - foreach (var sourceInfo in SourceInfo) { - if (sourceInfo.Offset == offset) - return true; - } + return (source, current.Line); + } - return false; + /// + /// Checks if the given bytecode offset is the first on a line of the source code + /// + public bool IsOnLineChange(int offset) { + foreach (var sourceInfo in SourceInfo) { + if (sourceInfo.Offset == offset) + return true; } - public bool TryGetOffsetAtSource(string source, int line, out int offset) { - string? currentSource = null; + return false; + } - int i = 0; - do { - var current = SourceInfo[i++]; + public bool TryGetOffsetAtSource(string source, int line, out int offset) { + string? currentSource = null; - if (current.File != null) - currentSource = ObjectTree.Strings[current.File.Value]; + int i = 0; + do { + var current = SourceInfo[i++]; - if (currentSource == source && current.Line == line) { - offset = current.Offset; - return true; - } - } while (i < SourceInfo.Count); + if (current.File != null) + currentSource = ObjectTree.Strings[current.File.Value]; - offset = 0; - return false; - } + if (currentSource == source && current.Line == line) { + offset = current.Offset; + return true; + } + } while (i < SourceInfo.Count); - public override ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) { - if (IsNullProc) { - if (!NullProcState.Pool.TryPop(out var nullState)) { - nullState = new NullProcState(); - } + offset = 0; + return false; + } - nullState.Initialize(this); - return nullState; + public override ProcState CreateState(DreamThread thread, DreamObject? src, DreamObject? usr, DreamProcArguments arguments) { + if (IsNullProc) { + if (!NullProcState.Pool.TryPop(out var nullState)) { + nullState = new NullProcState(); } - if (!DMProcState.Pool.TryPop(out var state)) { - state = new DMProcState(); - } + nullState.Initialize(this); + return nullState; + } - state.Initialize(this, thread, _maxStackSize, src, usr, arguments); - return state; + if (!DMProcState.Pool.TryPop(out var state)) { + state = new DMProcState(); } - private bool CheckIfNullProc() { - // We check for two possible patterns, entirely empty procs or pushing and returning self. - if (Bytecode.Length == 0 || Bytecode is [(byte)DreamProcOpcode.PushReferenceValue, 0x01, (byte)DreamProcOpcode.Return]) - return true; + state.Initialize(this, thread, _maxStackSize, src, usr, arguments); + return state; + } - return false; - } + private bool CheckIfNullProc() { + // We check for two possible patterns, entirely empty procs or pushing and returning self. + if (Bytecode.Length == 0 || Bytecode is [(byte)DreamProcOpcode.PushReferenceValue, 0x01, (byte)DreamProcOpcode.Return]) + return true; - private static List? GetArgumentNames(ProcDefinitionJson json) { - if (json.Arguments == null) { - return new(); - } else { - var argumentNames = new List(json.Arguments.Count); - argumentNames.AddRange(json.Arguments.Select(a => a.Name).ToArray()); - return argumentNames; - } + return false; + } + + private static List GetArgumentNames(ProcDefinitionJson json) { + if (json.Arguments == null) { + return new(); + } else { + var argumentNames = new List(json.Arguments.Count); + argumentNames.AddRange(json.Arguments.Select(a => a.Name).ToArray()); + return argumentNames; } + } - private static List? GetArgumentTypes(ProcDefinitionJson json) { - if (json.Arguments == null) { - return null; - } else { - var argumentTypes = new List(json.Arguments.Count); - argumentTypes.AddRange(json.Arguments.Select(a => (DreamValueType)a.Type)); - return argumentTypes; - } + private static List? GetArgumentTypes(ProcDefinitionJson json) { + if (json.Arguments == null) { + return null; + } else { + var argumentTypes = new List(json.Arguments.Count); + argumentTypes.AddRange(json.Arguments.Select(a => (DreamValueType)a.Type)); + return argumentTypes; } } +} - public sealed class NullProcState : ProcState { - public static readonly Stack Pool = new(); +public sealed class NullProcState : ProcState { + public static readonly Stack Pool = new(); - public override DreamProc? Proc => _proc; + public override DreamProc? Proc => _proc; - private DreamProc? _proc; + private DreamProc? _proc; - public override ProcStatus Resume() { - return ProcStatus.Returned; // do nothing heehoo - } + public override ProcStatus Resume() { + return ProcStatus.Returned; // do nothing heehoo + } - public override void AppendStackFrame(StringBuilder builder) { - throw new NotImplementedException(); - } + public override void AppendStackFrame(StringBuilder builder) { + throw new NotImplementedException(); + } - public void Initialize(DMProc proc) { - _proc = proc; - } + public void Initialize(DMProc proc) { + _proc = proc; + } - public override void Dispose() { - base.Dispose(); - _proc = null; - Pool.Push(this); + public override void Dispose() { + base.Dispose(); + _proc = null; + Pool.Push(this); + } +} + +public sealed class DMProcState : ProcState { + private delegate ProcStatus OpcodeHandler(DMProcState state); + + public static readonly Stack Pool = new(); + + private static readonly ArrayPool DreamValuePool = ArrayPool.Create(); + + #region Opcode Handlers + + //Human-readable friendly version, which will be converted to a more efficient lookup at runtime. + private static readonly Dictionary OpcodeHandlers = new() { + {DreamProcOpcode.BitShiftLeft, DMOpcodeHandlers.BitShiftLeft}, + {DreamProcOpcode.PushType, DMOpcodeHandlers.PushType}, + {DreamProcOpcode.PushString, DMOpcodeHandlers.PushString}, + {DreamProcOpcode.FormatString, DMOpcodeHandlers.FormatString}, + {DreamProcOpcode.SwitchCaseRange, DMOpcodeHandlers.SwitchCaseRange}, + {DreamProcOpcode.PushReferenceValue, DMOpcodeHandlers.PushReferenceValue}, + {DreamProcOpcode.Add, DMOpcodeHandlers.Add}, + {DreamProcOpcode.Assign, DMOpcodeHandlers.Assign}, + {DreamProcOpcode.Call, DMOpcodeHandlers.Call}, + {DreamProcOpcode.MultiplyReference, DMOpcodeHandlers.MultiplyReference}, + {DreamProcOpcode.JumpIfFalse, DMOpcodeHandlers.JumpIfFalse}, + {DreamProcOpcode.Jump, DMOpcodeHandlers.Jump}, + {DreamProcOpcode.CompareEquals, DMOpcodeHandlers.CompareEquals}, + {DreamProcOpcode.Return, DMOpcodeHandlers.Return}, + {DreamProcOpcode.PushNull, DMOpcodeHandlers.PushNull}, + {DreamProcOpcode.Subtract, DMOpcodeHandlers.Subtract}, + {DreamProcOpcode.CompareLessThan, DMOpcodeHandlers.CompareLessThan}, + {DreamProcOpcode.CompareGreaterThan, DMOpcodeHandlers.CompareGreaterThan}, + {DreamProcOpcode.BooleanAnd, DMOpcodeHandlers.BooleanAnd}, + {DreamProcOpcode.BooleanNot, DMOpcodeHandlers.BooleanNot}, + {DreamProcOpcode.DivideReference, DMOpcodeHandlers.DivideReference}, + {DreamProcOpcode.Negate, DMOpcodeHandlers.Negate}, + {DreamProcOpcode.Modulus, DMOpcodeHandlers.Modulus}, + {DreamProcOpcode.Append, DMOpcodeHandlers.Append}, + {DreamProcOpcode.AppendNoPush, DMOpcodeHandlers.AppendNoPush}, + {DreamProcOpcode.CreateRangeEnumerator, DMOpcodeHandlers.CreateRangeEnumerator}, + {DreamProcOpcode.Input, DMOpcodeHandlers.Input}, + {DreamProcOpcode.CompareLessThanOrEqual, DMOpcodeHandlers.CompareLessThanOrEqual}, + {DreamProcOpcode.CreateAssociativeList, DMOpcodeHandlers.CreateAssociativeList}, + {DreamProcOpcode.Remove, DMOpcodeHandlers.Remove}, + {DreamProcOpcode.DeleteObject, DMOpcodeHandlers.DeleteObject}, + {DreamProcOpcode.PushResource, DMOpcodeHandlers.PushResource}, + {DreamProcOpcode.CreateList, DMOpcodeHandlers.CreateList}, + {DreamProcOpcode.CallStatement, DMOpcodeHandlers.CallStatement}, + {DreamProcOpcode.BitAnd, DMOpcodeHandlers.BitAnd}, + {DreamProcOpcode.CompareNotEquals, DMOpcodeHandlers.CompareNotEquals}, + {DreamProcOpcode.PushProc, DMOpcodeHandlers.PushProc}, + {DreamProcOpcode.Divide, DMOpcodeHandlers.Divide}, + {DreamProcOpcode.Multiply, DMOpcodeHandlers.Multiply}, + {DreamProcOpcode.BitXorReference, DMOpcodeHandlers.BitXorReference}, + {DreamProcOpcode.BitXor, DMOpcodeHandlers.BitXor}, + {DreamProcOpcode.BitOr, DMOpcodeHandlers.BitOr}, + {DreamProcOpcode.BitNot, DMOpcodeHandlers.BitNot}, + {DreamProcOpcode.Combine, DMOpcodeHandlers.Combine}, + {DreamProcOpcode.CreateObject, DMOpcodeHandlers.CreateObject}, + {DreamProcOpcode.BooleanOr, DMOpcodeHandlers.BooleanOr}, + {DreamProcOpcode.CreateMultidimensionalList, DMOpcodeHandlers.CreateMultidimensionalList}, + {DreamProcOpcode.CompareGreaterThanOrEqual, DMOpcodeHandlers.CompareGreaterThanOrEqual}, + {DreamProcOpcode.SwitchCase, DMOpcodeHandlers.SwitchCase}, + {DreamProcOpcode.Mask, DMOpcodeHandlers.Mask}, + {DreamProcOpcode.Error, DMOpcodeHandlers.Error}, + {DreamProcOpcode.IsInList, DMOpcodeHandlers.IsInList}, + {DreamProcOpcode.PushFloat, DMOpcodeHandlers.PushFloat}, + {DreamProcOpcode.ModulusReference, DMOpcodeHandlers.ModulusReference}, + {DreamProcOpcode.CreateListEnumerator, DMOpcodeHandlers.CreateListEnumerator}, + {DreamProcOpcode.Enumerate, DMOpcodeHandlers.Enumerate}, + {DreamProcOpcode.DestroyEnumerator, DMOpcodeHandlers.DestroyEnumerator}, + {DreamProcOpcode.Browse, DMOpcodeHandlers.Browse}, + {DreamProcOpcode.BrowseResource, DMOpcodeHandlers.BrowseResource}, + {DreamProcOpcode.OutputControl, DMOpcodeHandlers.OutputControl}, + {DreamProcOpcode.BitShiftRight, DMOpcodeHandlers.BitShiftRight}, + {DreamProcOpcode.CreateFilteredListEnumerator, DMOpcodeHandlers.CreateFilteredListEnumerator}, + {DreamProcOpcode.Power, DMOpcodeHandlers.Power}, + {DreamProcOpcode.Prompt, DMOpcodeHandlers.Prompt}, + {DreamProcOpcode.Link, DMOpcodeHandlers.Link}, + {DreamProcOpcode.Ftp, DMOpcodeHandlers.Ftp}, + {DreamProcOpcode.Initial, DMOpcodeHandlers.Initial}, + {DreamProcOpcode.AsType, DMOpcodeHandlers.AsType}, + {DreamProcOpcode.IsType, DMOpcodeHandlers.IsType}, + {DreamProcOpcode.LocateCoord, DMOpcodeHandlers.LocateCoord}, + {DreamProcOpcode.Locate, DMOpcodeHandlers.Locate}, + {DreamProcOpcode.IsNull, DMOpcodeHandlers.IsNull}, + {DreamProcOpcode.Spawn, DMOpcodeHandlers.Spawn}, + {DreamProcOpcode.OutputReference, DMOpcodeHandlers.OutputReference}, + {DreamProcOpcode.Output, DMOpcodeHandlers.Output}, + {DreamProcOpcode.Pop, DMOpcodeHandlers.Pop}, + {DreamProcOpcode.Prob, DMOpcodeHandlers.Prob}, + {DreamProcOpcode.IsSaved, DMOpcodeHandlers.IsSaved}, + {DreamProcOpcode.PickUnweighted, DMOpcodeHandlers.PickUnweighted}, + {DreamProcOpcode.PickWeighted, DMOpcodeHandlers.PickWeighted}, + {DreamProcOpcode.Increment, DMOpcodeHandlers.Increment}, + {DreamProcOpcode.Decrement, DMOpcodeHandlers.Decrement}, + {DreamProcOpcode.CompareEquivalent, DMOpcodeHandlers.CompareEquivalent}, + {DreamProcOpcode.CompareNotEquivalent, DMOpcodeHandlers.CompareNotEquivalent}, + {DreamProcOpcode.Throw, DMOpcodeHandlers.Throw}, + {DreamProcOpcode.IsInRange, DMOpcodeHandlers.IsInRange}, + {DreamProcOpcode.MassConcatenation, DMOpcodeHandlers.MassConcatenation}, + {DreamProcOpcode.CreateTypeEnumerator, DMOpcodeHandlers.CreateTypeEnumerator}, + {DreamProcOpcode.PushGlobalVars, DMOpcodeHandlers.PushGlobalVars}, + {DreamProcOpcode.ModulusModulus, DMOpcodeHandlers.ModulusModulus}, + {DreamProcOpcode.ModulusModulusReference, DMOpcodeHandlers.ModulusModulusReference}, + {DreamProcOpcode.AssignInto, DMOpcodeHandlers.AssignInto}, + {DreamProcOpcode.JumpIfNull, DMOpcodeHandlers.JumpIfNull}, + {DreamProcOpcode.JumpIfNullNoPop, DMOpcodeHandlers.JumpIfNullNoPop}, + {DreamProcOpcode.JumpIfTrueReference, DMOpcodeHandlers.JumpIfTrueReference}, + {DreamProcOpcode.JumpIfFalseReference, DMOpcodeHandlers.JumpIfFalseReference}, + {DreamProcOpcode.DereferenceField, DMOpcodeHandlers.DereferenceField}, + {DreamProcOpcode.DereferenceIndex, DMOpcodeHandlers.DereferenceIndex}, + {DreamProcOpcode.IndexRefWithString, DMOpcodeHandlers.IndexRefWithString}, + {DreamProcOpcode.DereferenceCall, DMOpcodeHandlers.DereferenceCall}, + {DreamProcOpcode.PopReference, DMOpcodeHandlers.PopReference}, + {DreamProcOpcode.BitShiftLeftReference,DMOpcodeHandlers.BitShiftLeftReference}, + {DreamProcOpcode.BitShiftRightReference, DMOpcodeHandlers.BitShiftRightReference}, + {DreamProcOpcode.Try, DMOpcodeHandlers.Try}, + {DreamProcOpcode.TryNoValue, DMOpcodeHandlers.TryNoValue}, + {DreamProcOpcode.EndTry, DMOpcodeHandlers.EndTry}, + {DreamProcOpcode.Gradient, DMOpcodeHandlers.Gradient}, + {DreamProcOpcode.Sin, DMOpcodeHandlers.Sin}, + {DreamProcOpcode.Cos, DMOpcodeHandlers.Cos}, + {DreamProcOpcode.Tan, DMOpcodeHandlers.Tan}, + {DreamProcOpcode.ArcSin, DMOpcodeHandlers.ArcSin}, + {DreamProcOpcode.ArcCos, DMOpcodeHandlers.ArcCos}, + {DreamProcOpcode.ArcTan, DMOpcodeHandlers.ArcTan}, + {DreamProcOpcode.ArcTan2, DMOpcodeHandlers.ArcTan2}, + {DreamProcOpcode.Sqrt, DMOpcodeHandlers.Sqrt}, + {DreamProcOpcode.Log, DMOpcodeHandlers.Log}, + {DreamProcOpcode.LogE, DMOpcodeHandlers.LogE}, + {DreamProcOpcode.Abs, DMOpcodeHandlers.Abs}, + {DreamProcOpcode.EnumerateNoAssign, DMOpcodeHandlers.EnumerateNoAssign}, + {DreamProcOpcode.GetStep, DMOpcodeHandlers.GetStep}, + {DreamProcOpcode.Length, DMOpcodeHandlers.Length}, + {DreamProcOpcode.GetDir, DMOpcodeHandlers.GetDir}, + {DreamProcOpcode.DebuggerBreakpoint, DMOpcodeHandlers.DebuggerBreakpoint}, + {DreamProcOpcode.Rgb, DMOpcodeHandlers.Rgb}, + // Peephole optimizer opcode handlers + {DreamProcOpcode.NullRef, DMOpcodeHandlers.NullRef}, + {DreamProcOpcode.AssignNoPush, DMOpcodeHandlers.AssignNoPush}, + {DreamProcOpcode.PushRefAndDereferenceField, DMOpcodeHandlers.PushReferenceAndDereferenceField}, + {DreamProcOpcode.PushNRefs, DMOpcodeHandlers.PushNRefs}, + {DreamProcOpcode.PushNFloats, DMOpcodeHandlers.PushNFloats}, + {DreamProcOpcode.PushNStrings, DMOpcodeHandlers.PushNStrings}, + {DreamProcOpcode.PushNResources, DMOpcodeHandlers.PushNResources}, + {DreamProcOpcode.PushStringFloat, DMOpcodeHandlers.PushStringFloat}, + {DreamProcOpcode.SwitchOnFloat, DMOpcodeHandlers.SwitchOnFloat}, + {DreamProcOpcode.SwitchOnString, DMOpcodeHandlers.SwitchOnString}, + {DreamProcOpcode.JumpIfReferenceFalse, DMOpcodeHandlers.JumpIfReferenceFalse}, + {DreamProcOpcode.PushNOfStringFloats, DMOpcodeHandlers.PushNOfStringFloat}, + {DreamProcOpcode.CreateListNFloats, DMOpcodeHandlers.CreateListNFloats}, + {DreamProcOpcode.CreateListNStrings, DMOpcodeHandlers.CreateListNStrings}, + {DreamProcOpcode.CreateListNRefs, DMOpcodeHandlers.CreateListNRefs}, + {DreamProcOpcode.CreateListNResources, DMOpcodeHandlers.CreateListNResources}, + {DreamProcOpcode.IsTypeDirect, DMOpcodeHandlers.IsTypeDirect}, + {DreamProcOpcode.ReturnReferenceValue, DMOpcodeHandlers.ReturnReferenceValue}, + {DreamProcOpcode.ReturnFloat, DMOpcodeHandlers.ReturnFloat} + }; + + public static readonly unsafe delegate*[] OpcodeHandlersTable; + + #endregion + + public DreamManager DreamManager => _proc.DreamManager; + public ProcScheduler ProcScheduler => _proc.ProcScheduler; + public IDreamDebugManager DebugManager => _proc.DreamDebugManager; + + /// This stores our 'src' value. May be null! + public DreamObject? Instance; + + public DreamObject? Usr; + public int ArgumentCount; + private readonly Stack _catchPosition = new(); + private readonly Stack _catchVarIndex = new(); + private const int NoTryCatchVar = -1; + public readonly IDreamValueEnumerator?[] Enumerators = new IDreamValueEnumerator?[16]; + + public int ProgramCounter => _pc; + private int _pc; + + private bool _firstResume = true; + + // Contains both arguments (at index 0) and local vars (at index ArgumentCount) + private DreamValue[] _localVariables = default!; + + private DMProc _proc = default!; + public override DMProc Proc => _proc; + + /// Static initializer for maintainer friendly OpcodeHandlers to performance friendly _opcodeHandlers + static unsafe DMProcState() { + int maxOpcode = (int)OpcodeHandlers.Keys.Max(); + + OpcodeHandlersTable = new delegate*[256]; + foreach (var (dpo, handler) in OpcodeHandlers) { + OpcodeHandlersTable[(int) dpo] = (delegate*) handler.Method.MethodHandle.GetFunctionPointer(); + } + + var invalid = DMOpcodeHandlers.Invalid; + var invalidPtr = (delegate*)invalid.Method.MethodHandle.GetFunctionPointer(); + + OpcodeHandlersTable[0] = invalidPtr; + for (int i = maxOpcode + 1; i < 256; i++) { + OpcodeHandlersTable[i] = invalidPtr; } } - public sealed class DMProcState : ProcState { - private delegate ProcStatus OpcodeHandler(DMProcState state); - - public static readonly Stack Pool = new(); - - private static readonly ArrayPool _dreamValuePool = ArrayPool.Create(); - - #region Opcode Handlers - - //Human readable friendly version, which will be converted to a more efficient lookup at runtime. - private static readonly Dictionary _opcodeHandlers = new() { - {DreamProcOpcode.BitShiftLeft, DMOpcodeHandlers.BitShiftLeft}, - {DreamProcOpcode.PushType, DMOpcodeHandlers.PushType}, - {DreamProcOpcode.PushString, DMOpcodeHandlers.PushString}, - {DreamProcOpcode.FormatString, DMOpcodeHandlers.FormatString}, - {DreamProcOpcode.SwitchCaseRange, DMOpcodeHandlers.SwitchCaseRange}, - {DreamProcOpcode.PushReferenceValue, DMOpcodeHandlers.PushReferenceValue}, - {DreamProcOpcode.Add, DMOpcodeHandlers.Add}, - {DreamProcOpcode.Assign, DMOpcodeHandlers.Assign}, - {DreamProcOpcode.Call, DMOpcodeHandlers.Call}, - {DreamProcOpcode.MultiplyReference, DMOpcodeHandlers.MultiplyReference}, - {DreamProcOpcode.JumpIfFalse, DMOpcodeHandlers.JumpIfFalse}, - {DreamProcOpcode.Jump, DMOpcodeHandlers.Jump}, - {DreamProcOpcode.CompareEquals, DMOpcodeHandlers.CompareEquals}, - {DreamProcOpcode.Return, DMOpcodeHandlers.Return}, - {DreamProcOpcode.PushNull, DMOpcodeHandlers.PushNull}, - {DreamProcOpcode.Subtract, DMOpcodeHandlers.Subtract}, - {DreamProcOpcode.CompareLessThan, DMOpcodeHandlers.CompareLessThan}, - {DreamProcOpcode.CompareGreaterThan, DMOpcodeHandlers.CompareGreaterThan}, - {DreamProcOpcode.BooleanAnd, DMOpcodeHandlers.BooleanAnd}, - {DreamProcOpcode.BooleanNot, DMOpcodeHandlers.BooleanNot}, - {DreamProcOpcode.DivideReference, DMOpcodeHandlers.DivideReference}, - {DreamProcOpcode.Negate, DMOpcodeHandlers.Negate}, - {DreamProcOpcode.Modulus, DMOpcodeHandlers.Modulus}, - {DreamProcOpcode.Append, DMOpcodeHandlers.Append}, - {DreamProcOpcode.AppendNoPush, DMOpcodeHandlers.AppendNoPush}, - {DreamProcOpcode.CreateRangeEnumerator, DMOpcodeHandlers.CreateRangeEnumerator}, - {DreamProcOpcode.Input, DMOpcodeHandlers.Input}, - {DreamProcOpcode.CompareLessThanOrEqual, DMOpcodeHandlers.CompareLessThanOrEqual}, - {DreamProcOpcode.CreateAssociativeList, DMOpcodeHandlers.CreateAssociativeList}, - {DreamProcOpcode.Remove, DMOpcodeHandlers.Remove}, - {DreamProcOpcode.DeleteObject, DMOpcodeHandlers.DeleteObject}, - {DreamProcOpcode.PushResource, DMOpcodeHandlers.PushResource}, - {DreamProcOpcode.CreateList, DMOpcodeHandlers.CreateList}, - {DreamProcOpcode.CallStatement, DMOpcodeHandlers.CallStatement}, - {DreamProcOpcode.BitAnd, DMOpcodeHandlers.BitAnd}, - {DreamProcOpcode.CompareNotEquals, DMOpcodeHandlers.CompareNotEquals}, - {DreamProcOpcode.PushProc, DMOpcodeHandlers.PushProc}, - {DreamProcOpcode.Divide, DMOpcodeHandlers.Divide}, - {DreamProcOpcode.Multiply, DMOpcodeHandlers.Multiply}, - {DreamProcOpcode.BitXorReference, DMOpcodeHandlers.BitXorReference}, - {DreamProcOpcode.BitXor, DMOpcodeHandlers.BitXor}, - {DreamProcOpcode.BitOr, DMOpcodeHandlers.BitOr}, - {DreamProcOpcode.BitNot, DMOpcodeHandlers.BitNot}, - {DreamProcOpcode.Combine, DMOpcodeHandlers.Combine}, - {DreamProcOpcode.CreateObject, DMOpcodeHandlers.CreateObject}, - {DreamProcOpcode.BooleanOr, DMOpcodeHandlers.BooleanOr}, - {DreamProcOpcode.CreateMultidimensionalList, DMOpcodeHandlers.CreateMultidimensionalList}, - {DreamProcOpcode.CompareGreaterThanOrEqual, DMOpcodeHandlers.CompareGreaterThanOrEqual}, - {DreamProcOpcode.SwitchCase, DMOpcodeHandlers.SwitchCase}, - {DreamProcOpcode.Mask, DMOpcodeHandlers.Mask}, - {DreamProcOpcode.Error, DMOpcodeHandlers.Error}, - {DreamProcOpcode.IsInList, DMOpcodeHandlers.IsInList}, - {DreamProcOpcode.PushFloat, DMOpcodeHandlers.PushFloat}, - {DreamProcOpcode.ModulusReference, DMOpcodeHandlers.ModulusReference}, - {DreamProcOpcode.CreateListEnumerator, DMOpcodeHandlers.CreateListEnumerator}, - {DreamProcOpcode.Enumerate, DMOpcodeHandlers.Enumerate}, - {DreamProcOpcode.DestroyEnumerator, DMOpcodeHandlers.DestroyEnumerator}, - {DreamProcOpcode.Browse, DMOpcodeHandlers.Browse}, - {DreamProcOpcode.BrowseResource, DMOpcodeHandlers.BrowseResource}, - {DreamProcOpcode.OutputControl, DMOpcodeHandlers.OutputControl}, - {DreamProcOpcode.BitShiftRight, DMOpcodeHandlers.BitShiftRight}, - {DreamProcOpcode.CreateFilteredListEnumerator, DMOpcodeHandlers.CreateFilteredListEnumerator}, - {DreamProcOpcode.Power, DMOpcodeHandlers.Power}, - {DreamProcOpcode.Prompt, DMOpcodeHandlers.Prompt}, - {DreamProcOpcode.Link, DMOpcodeHandlers.Link}, - {DreamProcOpcode.Ftp, DMOpcodeHandlers.Ftp}, - {DreamProcOpcode.Initial, DMOpcodeHandlers.Initial}, - {DreamProcOpcode.AsType, DMOpcodeHandlers.AsType}, - {DreamProcOpcode.IsType, DMOpcodeHandlers.IsType}, - {DreamProcOpcode.LocateCoord, DMOpcodeHandlers.LocateCoord}, - {DreamProcOpcode.Locate, DMOpcodeHandlers.Locate}, - {DreamProcOpcode.IsNull, DMOpcodeHandlers.IsNull}, - {DreamProcOpcode.Spawn, DMOpcodeHandlers.Spawn}, - {DreamProcOpcode.OutputReference, DMOpcodeHandlers.OutputReference}, - {DreamProcOpcode.Output, DMOpcodeHandlers.Output}, - {DreamProcOpcode.Pop, DMOpcodeHandlers.Pop}, - {DreamProcOpcode.Prob, DMOpcodeHandlers.Prob}, - {DreamProcOpcode.IsSaved, DMOpcodeHandlers.IsSaved}, - {DreamProcOpcode.PickUnweighted, DMOpcodeHandlers.PickUnweighted}, - {DreamProcOpcode.PickWeighted, DMOpcodeHandlers.PickWeighted}, - {DreamProcOpcode.Increment, DMOpcodeHandlers.Increment}, - {DreamProcOpcode.Decrement, DMOpcodeHandlers.Decrement}, - {DreamProcOpcode.CompareEquivalent, DMOpcodeHandlers.CompareEquivalent}, - {DreamProcOpcode.CompareNotEquivalent, DMOpcodeHandlers.CompareNotEquivalent}, - {DreamProcOpcode.Throw, DMOpcodeHandlers.Throw}, - {DreamProcOpcode.IsInRange, DMOpcodeHandlers.IsInRange}, - {DreamProcOpcode.MassConcatenation, DMOpcodeHandlers.MassConcatenation}, - {DreamProcOpcode.CreateTypeEnumerator, DMOpcodeHandlers.CreateTypeEnumerator}, - {DreamProcOpcode.PushGlobalVars, DMOpcodeHandlers.PushGlobalVars}, - {DreamProcOpcode.ModulusModulus, DMOpcodeHandlers.ModulusModulus}, - {DreamProcOpcode.ModulusModulusReference, DMOpcodeHandlers.ModulusModulusReference}, - {DreamProcOpcode.AssignInto, DMOpcodeHandlers.AssignInto}, - {DreamProcOpcode.JumpIfNull, DMOpcodeHandlers.JumpIfNull}, - {DreamProcOpcode.JumpIfNullNoPop, DMOpcodeHandlers.JumpIfNullNoPop}, - {DreamProcOpcode.JumpIfTrueReference, DMOpcodeHandlers.JumpIfTrueReference}, - {DreamProcOpcode.JumpIfFalseReference, DMOpcodeHandlers.JumpIfFalseReference}, - {DreamProcOpcode.DereferenceField, DMOpcodeHandlers.DereferenceField}, - {DreamProcOpcode.DereferenceIndex, DMOpcodeHandlers.DereferenceIndex}, - {DreamProcOpcode.IndexRefWithString, DMOpcodeHandlers.IndexRefWithString}, - {DreamProcOpcode.DereferenceCall, DMOpcodeHandlers.DereferenceCall}, - {DreamProcOpcode.PopReference, DMOpcodeHandlers.PopReference}, - {DreamProcOpcode.BitShiftLeftReference,DMOpcodeHandlers.BitShiftLeftReference}, - {DreamProcOpcode.BitShiftRightReference, DMOpcodeHandlers.BitShiftRightReference}, - {DreamProcOpcode.Try, DMOpcodeHandlers.Try}, - {DreamProcOpcode.TryNoValue, DMOpcodeHandlers.TryNoValue}, - {DreamProcOpcode.EndTry, DMOpcodeHandlers.EndTry}, - {DreamProcOpcode.Gradient, DMOpcodeHandlers.Gradient}, - {DreamProcOpcode.Sin, DMOpcodeHandlers.Sin}, - {DreamProcOpcode.Cos, DMOpcodeHandlers.Cos}, - {DreamProcOpcode.Tan, DMOpcodeHandlers.Tan}, - {DreamProcOpcode.ArcSin, DMOpcodeHandlers.ArcSin}, - {DreamProcOpcode.ArcCos, DMOpcodeHandlers.ArcCos}, - {DreamProcOpcode.ArcTan, DMOpcodeHandlers.ArcTan}, - {DreamProcOpcode.ArcTan2, DMOpcodeHandlers.ArcTan2}, - {DreamProcOpcode.Sqrt, DMOpcodeHandlers.Sqrt}, - {DreamProcOpcode.Log, DMOpcodeHandlers.Log}, - {DreamProcOpcode.LogE, DMOpcodeHandlers.LogE}, - {DreamProcOpcode.Abs, DMOpcodeHandlers.Abs}, - {DreamProcOpcode.EnumerateNoAssign, DMOpcodeHandlers.EnumerateNoAssign}, - {DreamProcOpcode.GetStep, DMOpcodeHandlers.GetStep}, - {DreamProcOpcode.Length, DMOpcodeHandlers.Length}, - {DreamProcOpcode.GetDir, DMOpcodeHandlers.GetDir}, - {DreamProcOpcode.DebuggerBreakpoint, DMOpcodeHandlers.DebuggerBreakpoint}, - {DreamProcOpcode.Rgb, DMOpcodeHandlers.Rgb}, - // Peephole optimizer opcode handlers - {DreamProcOpcode.NullRef, DMOpcodeHandlers.NullRef}, - {DreamProcOpcode.AssignNoPush, DMOpcodeHandlers.AssignNoPush}, - {DreamProcOpcode.PushRefAndDereferenceField, DMOpcodeHandlers.PushReferenceAndDereferenceField}, - {DreamProcOpcode.PushNRefs, DMOpcodeHandlers.PushNRefs}, - {DreamProcOpcode.PushNFloats, DMOpcodeHandlers.PushNFloats}, - {DreamProcOpcode.PushNStrings, DMOpcodeHandlers.PushNStrings}, - {DreamProcOpcode.PushNResources, DMOpcodeHandlers.PushNResources}, - {DreamProcOpcode.PushStringFloat, DMOpcodeHandlers.PushStringFloat}, - {DreamProcOpcode.SwitchOnFloat, DMOpcodeHandlers.SwitchOnFloat}, - {DreamProcOpcode.SwitchOnString, DMOpcodeHandlers.SwitchOnString}, - {DreamProcOpcode.JumpIfReferenceFalse, DMOpcodeHandlers.JumpIfReferenceFalse}, - {DreamProcOpcode.PushNOfStringFloats, DMOpcodeHandlers.PushNOfStringFloat}, - {DreamProcOpcode.CreateListNFloats, DMOpcodeHandlers.CreateListNFloats}, - {DreamProcOpcode.CreateListNStrings, DMOpcodeHandlers.CreateListNStrings}, - {DreamProcOpcode.CreateListNRefs, DMOpcodeHandlers.CreateListNRefs}, - {DreamProcOpcode.CreateListNResources, DMOpcodeHandlers.CreateListNResources}, - {DreamProcOpcode.IsTypeDirect, DMOpcodeHandlers.IsTypeDirect}, - {DreamProcOpcode.ReturnReferenceValue, DMOpcodeHandlers.ReturnReferenceValue}, - {DreamProcOpcode.ReturnFloat, DMOpcodeHandlers.ReturnFloat} - }; - - public static readonly unsafe delegate*[] OpcodeHandlers; - - #endregion - - public DreamManager DreamManager => _proc.DreamManager; - public ProcScheduler ProcScheduler => _proc.ProcScheduler; - public IDreamDebugManager DebugManager => _proc.DreamDebugManager; - - /// This stores our 'src' value. May be null! - public DreamObject? Instance; - public DreamObject? Usr; - public int ArgumentCount; - private readonly Stack _catchPosition = new(); - private readonly Stack _catchVarIndex = new(); - public const int NoTryCatchVar = -1; - public IDreamValueEnumerator?[] Enumerators = new IDreamValueEnumerator?[16]; - - private int _pc = 0; - public int ProgramCounter => _pc; - - private bool _firstResume = true; - - // Contains both arguments (at index 0) and local vars (at index ArgumentCount) - private DreamValue[] _localVariables; - - private DMProc _proc; - public override DMProc Proc => _proc; - - /// Static initializer for maintainer friendly OpcodeHandlers to performance friendly _opcodeHandlers - static unsafe DMProcState() { - int maxOpcode = (int)_opcodeHandlers.Keys.Max(); - - OpcodeHandlers = new delegate*[256]; - foreach (var (dpo, handler) in _opcodeHandlers) { - OpcodeHandlers[(int) dpo] = (delegate*) handler.Method.MethodHandle.GetFunctionPointer(); - } + public DMProcState() { } + + private DMProcState(DMProcState other, DreamThread thread) { + base.Initialize(thread, other.WaitFor); + _proc = other._proc; + Instance = other.Instance; + Usr = other.Usr; + ArgumentCount = other.ArgumentCount; + _pc = other._pc; + _firstResume = false; + Result = other.Result; + + _stack = DreamValuePool.Rent(other._stack.Length); + _localVariables = DreamValuePool.Rent(other._localVariables.Length); + Array.Copy(other._localVariables, _localVariables, other._localVariables.Length); + } - var invalid = DMOpcodeHandlers.Invalid; - var invalidPtr = (delegate*)invalid.Method.MethodHandle.GetFunctionPointer(); + public void Initialize(DMProc proc, DreamThread thread, int maxStackSize, DreamObject? instance, DreamObject? usr, DreamProcArguments arguments) { + base.Initialize(thread, (proc.Attributes & ProcAttributes.DisableWaitfor) != ProcAttributes.DisableWaitfor); + _proc = proc; + Instance = instance; + Usr = usr; + ArgumentCount = Math.Max(arguments.Count, _proc.ArgumentNames?.Count ?? 0); + _localVariables = DreamValuePool.Rent(256); + _stack = DreamValuePool.Rent(maxStackSize); + _firstResume = true; - OpcodeHandlers[0] = invalidPtr; - for (int i = maxOpcode + 1; i < 256; i++) { - OpcodeHandlers[i] = invalidPtr; - } - } - - public DMProcState() { } - - private DMProcState(DMProcState other, DreamThread thread) { - base.Initialize(thread, other.WaitFor); - _proc = other._proc; - Instance = other.Instance; - Usr = other.Usr; - ArgumentCount = other.ArgumentCount; - _pc = other._pc; - _firstResume = false; - Result = other.Result; - - _stack = _dreamValuePool.Rent(other._stack.Length); - _localVariables = _dreamValuePool.Rent(other._localVariables.Length); - Array.Copy(other._localVariables, _localVariables, other._localVariables.Length); + for (int i = 0; i < ArgumentCount; i++) { + _localVariables[i] = arguments.GetArgument(i); } + } - public void Initialize(DMProc proc, DreamThread thread, int maxStackSize, DreamObject? instance, DreamObject? usr, DreamProcArguments arguments) { - base.Initialize(thread, (proc.Attributes & ProcAttributes.DisableWaitfor) != ProcAttributes.DisableWaitfor); - _proc = proc; - Instance = instance; - Usr = usr; - ArgumentCount = Math.Max(arguments.Count, _proc.ArgumentNames?.Count ?? 0); - _localVariables = _dreamValuePool.Rent(256); - _stack = _dreamValuePool.Rent(maxStackSize); - _firstResume = true; - - for (int i = 0; i < ArgumentCount; i++) { - _localVariables[i] = arguments.GetArgument(i); - } + public override unsafe ProcStatus Resume() { + if (Instance?.Deleted == true) { + return ProcStatus.Returned; } - public override unsafe ProcStatus Resume() { - if (Instance?.Deleted == true) { - return ProcStatus.Returned; - } - #if TOOLS if (_firstResume) { DebugManager.HandleFirstResume(this); @@ -420,732 +422,734 @@ public override unsafe ProcStatus Resume() { } #endif - var procBytecode = _proc.Bytecode; + var procBytecode = _proc.Bytecode; - if (procBytecode.Length == 0) - return ProcStatus.Returned; + if (procBytecode.Length == 0) + return ProcStatus.Returned; - fixed (delegate** handlers = &OpcodeHandlers[0]) { - fixed (byte* bytecode = &procBytecode[0]) { - var l = procBytecode.Length; // The length never changes so we stick it in a register. + fixed (delegate** handlers = &OpcodeHandlersTable[0]) { + fixed (byte* bytecode = &procBytecode[0]) { + var l = procBytecode.Length; // The length never changes so we stick it in a register. - while (_pc < l) { + while (_pc < l) { #if TOOLS DebugManager.HandleInstruction(this); #endif - int opcode = bytecode[_pc]; - _pc += 1; + int opcode = bytecode[_pc]; + _pc += 1; - var handler = handlers[opcode]; - var status = handler(this); + var handler = handlers[opcode]; + var status = handler(this); - if (status != ProcStatus.Continue) { - return status; - } + if (status != ProcStatus.Continue) { + return status; } } } - - return ProcStatus.Returned; } - public override void ReturnedInto(DreamValue value) { - Push(value); - } - - public override void AppendStackFrame(StringBuilder builder) { - if (Proc.OwningType != Proc.ObjectTree.Root) { - builder.Append(Proc.OwningType); - builder.Append('/'); - } + return ProcStatus.Returned; + } - builder.Append(Proc.Name); + public override void ReturnedInto(DreamValue value) { + Push(value); + } - // Subtract 1 because _pc may have been advanced to the next line - var location = Proc.GetSourceAtOffset(_pc - 1); - builder.Append(' '); - builder.Append(location.Source); - builder.Append(':'); - builder.Append(location.Line); + public override void AppendStackFrame(StringBuilder builder) { + if (Proc.OwningType != Proc.ObjectTree.Root) { + builder.Append(Proc.OwningType); + builder.Append('/'); } - public (string, int) GetCurrentSource() { - return Proc.GetSourceAtOffset(_pc - 1); - } + builder.Append(Proc.Name); - public void Jump(int position) { - _pc = position; - } + // Subtract 1 because _pc may have been advanced to the next line + var location = Proc.GetSourceAtOffset(_pc - 1); + builder.Append(' '); + builder.Append(location.Source); + builder.Append(':'); + builder.Append(location.Line); + } - public void SetReturn(DreamValue value) { - Result = value; - } + public (string, int) GetCurrentSource() { + return Proc.GetSourceAtOffset(_pc - 1); + } - public ProcStatus Call(DreamProc proc, DreamObject? src, DreamProcArguments arguments) { - if (proc is NativeProc p) { - // Skip a whole song and dance. - Push(p.Call(Thread, src, Usr, arguments)); - return ProcStatus.Continue; - } + public void Jump(int position) { + _pc = position; + } + + public void SetReturn(DreamValue value) { + Result = value; + } - var state = proc.CreateState(Thread, src, Usr, arguments); - Thread.PushProcState(state); - if (proc is AsyncNativeProc) // Hack to ensure sleeping native procs will return our value in a no-waitfor context - state.Result = Result; - return ProcStatus.Called; + public ProcStatus Call(DreamProc proc, DreamObject? src, DreamProcArguments arguments) { + if (proc is NativeProc p) { + // Skip a whole song and dance. + Push(p.Call(Thread, src, Usr, arguments)); + return ProcStatus.Continue; } - public DreamThread Spawn() { - var thread = new DreamThread(Proc.ToString()); + var state = proc.CreateState(Thread, src, Usr, arguments); + Thread.PushProcState(state); + if (proc is AsyncNativeProc) // Hack to ensure sleeping native procs will return our value in a no-waitfor context + state.Result = Result; + return ProcStatus.Called; + } - var state = new DMProcState(this, thread); - thread.PushProcState(state); + public DreamThread Spawn() { + var thread = new DreamThread(Proc.ToString()); - return thread; - } + var state = new DMProcState(this, thread); + thread.PushProcState(state); - public void StartTryBlock(int catchPosition, int catchVarIndex = NoTryCatchVar) { - if (catchVarIndex != NoTryCatchVar) - catchVarIndex += ArgumentCount; // We're given a local var index so we need to account for our arguments - - _catchPosition.Push(catchPosition); - _catchVarIndex.Push(catchVarIndex); - } + return thread; + } - public void EndTryBlock() { - _catchPosition.Pop(); - _catchVarIndex.Pop(); - } + public void StartTryBlock(int catchPosition, int catchVarIndex = NoTryCatchVar) { + if (catchVarIndex != NoTryCatchVar) + catchVarIndex += ArgumentCount; // We're given a local var index so we need to account for our arguments - public override bool IsCatching() => _catchPosition.Count > 0; + _catchPosition.Push(catchPosition); + _catchVarIndex.Push(catchVarIndex); + } - public override void CatchException(Exception exception) { - if (!IsCatching()) - base.CatchException(exception); + public void EndTryBlock() { + _catchPosition.Pop(); + _catchVarIndex.Pop(); + } - Jump(_catchPosition.Pop()); - var varIdx = _catchVarIndex.Pop(); - if (varIdx != NoTryCatchVar) { - DreamValue value; + public override bool IsCatching() => _catchPosition.Count > 0; - if (exception is DMThrowException throwException) - value = throwException.Value; - else - value = new DreamValue(exception.Message); // TODO: Probably need to create an /exception + public override void CatchException(Exception exception) { + if (!IsCatching()) + base.CatchException(exception); - _localVariables[varIdx] = value; - } - } + Jump(_catchPosition.Pop()); + var varIdx = _catchVarIndex.Pop(); + if (varIdx != NoTryCatchVar) { + DreamValue value; - public override void Dispose() { - base.Dispose(); + if (exception is DMThrowException throwException) + value = throwException.Value; + else + value = new DreamValue(exception.Message); // TODO: Probably need to create an /exception - Instance = null; - Usr = null; - ArgumentCount = 0; - Array.Clear(Enumerators); - _pc = 0; - _proc = null; + _localVariables[varIdx] = value; + } + } - _dreamValuePool.Return(_stack); - _stackIndex = 0; - _stack = null; + public override void Dispose() { + base.Dispose(); - _dreamValuePool.Return(_localVariables, true); - _localVariables = null; + Instance = null; + Usr = null; + ArgumentCount = 0; + Array.Clear(Enumerators); + _pc = 0; + _proc = null!; - _catchPosition.Clear(); - _catchVarIndex.Clear(); + DreamValuePool.Return(_stack); + _stackIndex = 0; + _stack = null!; - Pool.Push(this); - } + DreamValuePool.Return(_localVariables, true); + _localVariables = null!; - public ReadOnlySpan GetArguments() { - return _localVariables.AsSpan(0, ArgumentCount); - } + _catchPosition.Clear(); + _catchVarIndex.Clear(); - public void SetArgument(int id, DreamValue value) { - if (id < 0 || id >= ArgumentCount) - throw new IndexOutOfRangeException($"Given argument id ({id}) was out of range"); + Pool.Push(this); + } - _localVariables[id] = value; - } + public ReadOnlySpan GetArguments() { + return _localVariables.AsSpan(0, ArgumentCount); + } - #region Stack - private DreamValue[] _stack; - private int _stackIndex = 0; - public ReadOnlyMemory DebugStack() => _stack.AsMemory(0, _stackIndex); + public void SetArgument(int id, DreamValue value) { + if (id < 0 || id >= ArgumentCount) + throw new IndexOutOfRangeException($"Given argument id ({id}) was out of range"); - public void Push(DreamValue value) { - _stack[_stackIndex] = value; - // ++ sucks for the compiler - _stackIndex += 1; - } + _localVariables[id] = value; + } - public DreamValue Pop() { - // -- sucks for the compiler - _stackIndex -= 1; - return _stack[_stackIndex]; - } + #region Stack - public void PopDrop() { - DebugTools.Assert(_stackIndex > 0, "Attempted to PopDrop with a stack index of (or below?) 0"); - _stackIndex -= 1; - } + private DreamValue[] _stack = default!; + private int _stackIndex; + public ReadOnlyMemory DebugStack() => _stack.AsMemory(0, _stackIndex); - /// - /// Pops multiple values off the stack - /// - /// Amount of values to pop - /// A ReadOnlySpan of the popped values, in FIFO order - public ReadOnlySpan PopCount(int count) { - _stackIndex -= count; + public void Push(DreamValue value) { + _stack[_stackIndex] = value; + // ++ sucks for the compiler + _stackIndex += 1; + } - return _stack.AsSpan(_stackIndex, count); - } + public DreamValue Pop() { + // -- sucks for the compiler + _stackIndex -= 1; + return _stack[_stackIndex]; + } - public DreamValue Peek() { - return _stack[_stackIndex - 1]; - } + public void PopDrop() { + DebugTools.Assert(_stackIndex > 0, "Attempted to PopDrop with a stack index of (or below?) 0"); + _stackIndex -= 1; + } - /// - /// Pops arguments off the stack and returns them in DreamProcArguments - /// - /// The target proc we're calling. If null, named args or arglist() cannot be used. - /// The source of the arguments - /// The amount of items the arguments have on the stack - /// The arguments in a DreamProcArguments struct - public DreamProcArguments PopProcArguments(DreamProc? proc, DMCallArgumentsType argumentsType, int argumentStackSize) { - var values = PopCount(argumentStackSize); - - return CreateProcArguments(values, proc, argumentsType, argumentStackSize); - } - #endregion - - #region Operands - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadByte() { - var r = _proc.Bytecode[_pc]; - _pc += 1; - return r; - } + /// + /// Pops multiple values off the stack + /// + /// Amount of values to pop + /// A ReadOnlySpan of the popped values, in FIFO order + public ReadOnlySpan PopCount(int count) { + _stackIndex -= count; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int ReadInt() { - int value = BitConverter.ToInt32(_proc.Bytecode, _pc); - _pc += 4; + return _stack.AsSpan(_stackIndex, count); + } - return value; - } + public DreamValue Peek() { + return _stack[_stackIndex - 1]; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public float ReadFloat() { - float value = BitConverter.ToSingle(_proc.Bytecode, _pc); - _pc += 4; + /// + /// Pops arguments off the stack and returns them in DreamProcArguments + /// + /// The target proc we're calling. If null, named args or arglist() cannot be used. + /// The source of the arguments + /// The amount of items the arguments have on the stack + /// The arguments in a DreamProcArguments struct + public DreamProcArguments PopProcArguments(DreamProc? proc, DMCallArgumentsType argumentsType, int argumentStackSize) { + var values = PopCount(argumentStackSize); + + return CreateProcArguments(values, proc, argumentsType, argumentStackSize); + } - return value; - } + #endregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public string ReadString() { - int stringId = ReadInt(); + #region Operands - return ResolveString(stringId); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadByte() { + var r = _proc.Bytecode[_pc]; + _pc += 1; + return r; + } - public string ResolveString(int stringId) { - return Proc.ObjectTree.Strings[stringId]; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int ReadInt() { + int value = BitConverter.ToInt32(_proc.Bytecode, _pc); + _pc += 4; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public DreamReference ReadReference() { - DMReference.Type refType = (DMReference.Type)ReadByte(); - - switch (refType) { - case DMReference.Type.Src: - case DMReference.Type.Self: - case DMReference.Type.Usr: - case DMReference.Type.Args: - case DMReference.Type.World: - case DMReference.Type.SuperProc: - case DMReference.Type.ListIndex: - return new DreamReference(refType, 0); - case DMReference.Type.Argument: - case DMReference.Type.Local: - return new DreamReference(refType, ReadByte()); - case DMReference.Type.Global: - case DMReference.Type.GlobalProc: - case DMReference.Type.Field: - case DMReference.Type.SrcField: - case DMReference.Type.SrcProc: - return new DreamReference(refType, ReadInt()); - default: { - ThrowInvalidReferenceType(refType); - return default; - } - } - } + return value; + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowInvalidReferenceType(DMReference.Type type) { - throw new Exception($"Invalid reference type {type}"); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float ReadFloat() { + float value = BitConverter.ToSingle(_proc.Bytecode, _pc); + _pc += 4; - public (DMCallArgumentsType Type, int StackSize) ReadProcArguments() { - return ((DMCallArgumentsType) ReadByte(), ReadInt()); - } + return value; + } - #endregion + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string ReadString() { + int stringId = ReadInt(); - #region References + return ResolveString(stringId); + } - /// - /// Takes a DMReference with a type and returns the value being indexed - /// as well as what it's being indexed with. - /// - /// A ListIndex DMReference - public void GetIndexReferenceValues(DreamReference reference, out DreamValue index, out DreamValue indexing, bool peek = false) { - if (reference.Type != DMReference.Type.ListIndex) - ThrowReferenceNotListIndex(); + public string ResolveString(int stringId) { + return Proc.ObjectTree.Strings[stringId]; + } - index = _stack[_stackIndex - 1]; - indexing = _stack[_stackIndex - 2]; - if (!peek) - _stackIndex -= 2; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public DreamReference ReadReference() { + DMReference.Type refType = (DMReference.Type)ReadByte(); + + switch (refType) { + case DMReference.Type.Src: + case DMReference.Type.Self: + case DMReference.Type.Usr: + case DMReference.Type.Args: + case DMReference.Type.World: + case DMReference.Type.SuperProc: + case DMReference.Type.ListIndex: + return new DreamReference(refType, 0); + case DMReference.Type.Argument: + case DMReference.Type.Local: + return new DreamReference(refType, ReadByte()); + case DMReference.Type.Global: + case DMReference.Type.GlobalProc: + case DMReference.Type.Field: + case DMReference.Type.SrcField: + case DMReference.Type.SrcProc: + return new DreamReference(refType, ReadInt()); + default: { + ThrowInvalidReferenceType(refType); + return default; + } } + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowReferenceNotListIndex() { - throw new ArgumentException("Reference was not a ListIndex type"); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidReferenceType(DMReference.Type type) { + throw new Exception($"Invalid reference type {type}"); + } - public void AssignReference(DreamReference reference, DreamValue value) { - switch (reference.Type) { - case DMReference.Type.Self: Result = value; break; - case DMReference.Type.Argument: SetArgument(reference.Value, value); break; - case DMReference.Type.Local: _localVariables[ArgumentCount + reference.Value] = value; break; - case DMReference.Type.SrcField: Instance.SetVariable(ResolveString(reference.Value), value); break; - case DMReference.Type.Global: DreamManager.Globals[reference.Value] = value; break; - case DMReference.Type.Src: - //TODO: src can be assigned to non-DreamObject values - if (!value.TryGetValueAsDreamObject(out Instance)) { - ThrowCannotAssignSrcTo(value); - } + public (DMCallArgumentsType Type, int StackSize) ReadProcArguments() { + return ((DMCallArgumentsType) ReadByte(), ReadInt()); + } - break; - case DMReference.Type.Usr: - //TODO: usr can be assigned to non-DreamObject values - if (!value.TryGetValueAsDreamObject(out Usr)) { - ThrowCannotAssignUsrTo(value); - } + #endregion + + #region References + + /// + /// Takes a DMReference with a type and returns the value being indexed + /// as well as what it's being indexed with. + /// + /// A ListIndex DMReference + /// What index is being accessed + /// What is being indexed + /// Peek the stack instead of popping + public void GetIndexReferenceValues(DreamReference reference, out DreamValue index, out DreamValue indexing, bool peek = false) { + if (reference.Type != DMReference.Type.ListIndex) + ThrowReferenceNotListIndex(); + + index = _stack[_stackIndex - 1]; + indexing = _stack[_stackIndex - 2]; + if (!peek) + _stackIndex -= 2; + } - break; - case DMReference.Type.Field: { - DreamValue owner = Pop(); - if (!owner.TryGetValueAsDreamObject(out var ownerObj) || ownerObj == null) - ThrowCannotAssignFieldOn(reference, owner); + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowReferenceNotListIndex() { + throw new ArgumentException("Reference was not a ListIndex type"); + } - ownerObj!.SetVariable(ResolveString(reference.Value), value); - break; + public void AssignReference(DreamReference reference, DreamValue value) { + switch (reference.Type) { + case DMReference.Type.Self: Result = value; break; + case DMReference.Type.Argument: SetArgument(reference.Value, value); break; + case DMReference.Type.Local: _localVariables[ArgumentCount + reference.Value] = value; break; + case DMReference.Type.SrcField: Instance.SetVariable(ResolveString(reference.Value), value); break; + case DMReference.Type.Global: DreamManager.Globals[reference.Value] = value; break; + case DMReference.Type.Src: + //TODO: src can be assigned to non-DreamObject values + if (!value.TryGetValueAsDreamObject(out Instance)) { + ThrowCannotAssignSrcTo(value); } - case DMReference.Type.ListIndex: { - GetIndexReferenceValues(reference, out var index, out var indexing); - if (indexing.TryGetValueAsDreamObject(out var dreamObject) && dreamObject != null) { - dreamObject.OperatorIndexAssign(index, this, value); - } else { - ThrowCannotAssignListIndex(index, indexing); - } + break; + case DMReference.Type.Usr: + //TODO: usr can be assigned to non-DreamObject values + if (!value.TryGetValueAsDreamObject(out Usr)) { + ThrowCannotAssignUsrTo(value); + } + + break; + case DMReference.Type.Field: { + DreamValue owner = Pop(); + if (!owner.TryGetValueAsDreamObject(out var ownerObj) || ownerObj == null) + ThrowCannotAssignFieldOn(reference, owner); - break; + ownerObj!.SetVariable(ResolveString(reference.Value), value); + break; + } + case DMReference.Type.ListIndex: { + GetIndexReferenceValues(reference, out var index, out var indexing); + + if (indexing.TryGetValueAsDreamObject(out var dreamObject) && dreamObject != null) { + dreamObject.OperatorIndexAssign(index, this, value); + } else { + ThrowCannotAssignListIndex(index, indexing); } - default: - ThrowCannotAssignReferenceType(reference); - break; + + break; } + default: + ThrowCannotAssignReferenceType(reference); + break; } + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotAssignReferenceType(DreamReference reference) { - throw new Exception($"Cannot assign to reference type {reference.Type}"); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotAssignReferenceType(DreamReference reference) { + throw new Exception($"Cannot assign to reference type {reference.Type}"); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotAssignListIndex(DreamValue index, DreamValue indexing) { - throw new Exception($"Cannot assign to index {index} of {indexing}"); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotAssignListIndex(DreamValue index, DreamValue indexing) { + throw new Exception($"Cannot assign to index {index} of {indexing}"); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private void ThrowCannotAssignFieldOn(DreamReference reference, DreamValue owner) { - throw new Exception($"Cannot assign field \"{ResolveString(reference.Value)}\" on {owner}"); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowCannotAssignFieldOn(DreamReference reference, DreamValue owner) { + throw new Exception($"Cannot assign field \"{ResolveString(reference.Value)}\" on {owner}"); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotAssignSrcTo(DreamValue value) { - throw new Exception($"Cannot assign src to {value}"); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotAssignSrcTo(DreamValue value) { + throw new Exception($"Cannot assign src to {value}"); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotAssignUsrTo(DreamValue value) { - throw new Exception($"Cannot assign usr to {value}"); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotAssignUsrTo(DreamValue value) { + throw new Exception($"Cannot assign usr to {value}"); + } - public DreamValue GetReferenceValue(DreamReference reference, bool peek = false) { - switch (reference.Type) { - case DMReference.Type.Src: return new(Instance); - case DMReference.Type.Usr: return new(Usr); - case DMReference.Type.Self: return Result; - case DMReference.Type.Global: return DreamManager.Globals[reference.Value]; - case DMReference.Type.Argument: return _localVariables[reference.Value]; - case DMReference.Type.Local: return _localVariables[ArgumentCount + reference.Value]; - case DMReference.Type.Args: return new(new ProcArgsList(Proc.ObjectTree.List.ObjectDefinition, this)); - case DMReference.Type.World: return new(DreamManager.WorldInstance); - case DMReference.Type.Field: { - DreamValue owner = peek ? Peek() : Pop(); - - return DereferenceField(owner, ResolveString(reference.Value)); - } - case DMReference.Type.SrcField: { - var fieldName = ResolveString(reference.Value); - if (Instance == null) - ThrowCannotGetFieldSrcGlobalProc(fieldName); - if (!Instance!.TryGetVariable(fieldName, out var fieldValue)) - ThrowTypeHasNoField(fieldName); - - return fieldValue; - } - case DMReference.Type.ListIndex: { - GetIndexReferenceValues(reference, out var index, out var indexing, peek); + public DreamValue GetReferenceValue(DreamReference reference, bool peek = false) { + switch (reference.Type) { + case DMReference.Type.Src: return new(Instance); + case DMReference.Type.Usr: return new(Usr); + case DMReference.Type.Self: return Result; + case DMReference.Type.Global: return DreamManager.Globals[reference.Value]; + case DMReference.Type.Argument: return _localVariables[reference.Value]; + case DMReference.Type.Local: return _localVariables[ArgumentCount + reference.Value]; + case DMReference.Type.Args: return new(new ProcArgsList(Proc.ObjectTree.List.ObjectDefinition, this)); + case DMReference.Type.World: return new(DreamManager.WorldInstance); + case DMReference.Type.Field: { + DreamValue owner = peek ? Peek() : Pop(); + + return DereferenceField(owner, ResolveString(reference.Value)); + } + case DMReference.Type.SrcField: { + var fieldName = ResolveString(reference.Value); + if (Instance == null) + ThrowCannotGetFieldSrcGlobalProc(fieldName); + if (!Instance!.TryGetVariable(fieldName, out var fieldValue)) + ThrowTypeHasNoField(fieldName); - return GetIndex(indexing, index, this); - } - default: - ThrowCannotGetValueOfReferenceType(reference); - return DreamValue.Null; + return fieldValue; } - } + case DMReference.Type.ListIndex: { + GetIndexReferenceValues(reference, out var index, out var indexing, peek); - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotGetValueOfReferenceType(DreamReference reference) { - throw new Exception($"Cannot get value of reference type {reference.Type}"); + return GetIndex(indexing, index, this); + } + default: + ThrowCannotGetValueOfReferenceType(reference); + return DreamValue.Null; } + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotGetFieldSrcGlobalProc(string fieldName) { - throw new Exception($"Cannot get field src.{fieldName} in global proc"); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotGetValueOfReferenceType(DreamReference reference) { + throw new Exception($"Cannot get value of reference type {reference.Type}"); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private void ThrowTypeHasNoField(string fieldName) { - throw new Exception($"Type {Instance!.ObjectDefinition.Type} has no field called \"{fieldName}\""); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotGetFieldSrcGlobalProc(string fieldName) { + throw new Exception($"Cannot get field src.{fieldName} in global proc"); + } - public void PopReference(DreamReference reference) { - switch (reference.Type) { - case DMReference.Type.Src: - case DMReference.Type.Usr: - case DMReference.Type.Self: - case DMReference.Type.Global: - case DMReference.Type.GlobalProc: - case DMReference.Type.Argument: - case DMReference.Type.Local: - case DMReference.Type.Args: - case DMReference.Type.SrcField: - return; - case DMReference.Type.ListIndex: - PopDrop(); - - // Fallthrough to the below case ends up with more performant generated code - goto case DMReference.Type.Field; - case DMReference.Type.Field: - PopDrop(); - return; - default: ThrowPopInvalidType(reference.Type); - return; - } - } + [MethodImpl(MethodImplOptions.NoInlining)] + private void ThrowTypeHasNoField(string fieldName) { + throw new Exception($"Type {Instance!.ObjectDefinition.Type} has no field called \"{fieldName}\""); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowPopInvalidType(DMReference.Type type) { - throw new Exception($"Cannot pop stack values of reference type {type}"); + public void PopReference(DreamReference reference) { + switch (reference.Type) { + case DMReference.Type.Src: + case DMReference.Type.Usr: + case DMReference.Type.Self: + case DMReference.Type.Global: + case DMReference.Type.GlobalProc: + case DMReference.Type.Argument: + case DMReference.Type.Local: + case DMReference.Type.Args: + case DMReference.Type.SrcField: + return; + case DMReference.Type.ListIndex: + PopDrop(); + + // Fallthrough to the below case ends up with more performant generated code + goto case DMReference.Type.Field; + case DMReference.Type.Field: + PopDrop(); + return; + default: ThrowPopInvalidType(reference.Type); + return; } + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowPopInvalidType(DMReference.Type type) { + throw new Exception($"Cannot pop stack values of reference type {type}"); + } - public DreamValue DereferenceField(DreamValue owner, string field) { - if (owner.TryGetValueAsDreamObject(out var ownerObj) && ownerObj != null) { - if (!ownerObj.TryGetVariable(field, out var fieldValue)) - ThrowTypeHasNoField(field, ownerObj); + public DreamValue DereferenceField(DreamValue owner, string field) { + if (owner.TryGetValueAsDreamObject(out var ownerObj)) { + if (!ownerObj.TryGetVariable(field, out var fieldValue)) + ThrowTypeHasNoField(field, ownerObj); - return fieldValue; - } else if (owner.TryGetValueAsProc(out var ownerProc)) { - return ownerProc.GetField(field); - } else if (owner.TryGetValueAsAppearance(out var appearance)) { - if (!Proc.AtomManager.IsValidAppearanceVar(field)) - ThrowInvalidAppearanceVar(field); - - return Proc.AtomManager.GetAppearanceVar(appearance, field); - } else if (owner.TryGetValueAsType(out var ownerType) && ownerType.ObjectDefinition.Variables.TryGetValue(field, out var val)) { - return val; // equivalent to initial() - } + return fieldValue; + } else if (owner.TryGetValueAsProc(out var ownerProc)) { + return ownerProc.GetField(field); + } else if (owner.TryGetValueAsAppearance(out var appearance)) { + if (!Proc.AtomManager.IsValidAppearanceVar(field)) + ThrowInvalidAppearanceVar(field); - ThrowCannotGetFieldFromOwner(owner, field); - return DreamValue.Null; + return Proc.AtomManager.GetAppearanceVar(appearance, field); + } else if (owner.TryGetValueAsType(out var ownerType) && ownerType.ObjectDefinition.Variables.TryGetValue(field, out var val)) { + return val; // equivalent to initial() } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotGetFieldFromOwner(DreamValue owner, string field) { - throw new Exception($"Cannot get field \"{field}\" from {owner}"); - } + ThrowCannotGetFieldFromOwner(owner, field); + return DreamValue.Null; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotGetFieldFromOwner(DreamValue owner, string field) { + throw new Exception($"Cannot get field \"{field}\" from {owner}"); + } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowInvalidAppearanceVar(string field) { - throw new Exception($"Invalid appearance var \"{field}\""); + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowInvalidAppearanceVar(string field) { + throw new Exception($"Invalid appearance var \"{field}\""); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowTypeHasNoField(string field, DreamObject ownerObj) { + throw new Exception($"Type {ownerObj.ObjectDefinition.Type} has no field called \"{field}\""); + } + + public DreamValue GetIndex(DreamValue indexing, DreamValue index, DMProcState state) { + if (indexing.TryGetValueAsDreamList(out var listObj)) { + return listObj.GetValue(index); } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowTypeHasNoField(string field, DreamObject? ownerObj) { - throw new Exception($"Type {ownerObj.ObjectDefinition.Type} has no field called \"{field}\""); + if (indexing.TryGetValueAsString(out string? strValue)) { + if (!index.TryGetValueAsInteger(out int strIndex)) + ThrowAttemptedToIndexString(index); + + char c = strValue[strIndex - 1]; + return new DreamValue(Convert.ToString(c)); } - public DreamValue GetIndex(DreamValue indexing, DreamValue index, DMProcState state) { - if (indexing.TryGetValueAsDreamList(out var listObj)) { - return listObj.GetValue(index); + if (indexing.TryGetValueAsDreamObject(out var dreamObject)) { + if (dreamObject != null) { + return dreamObject.OperatorIndex(index, state); } + } - if (indexing.TryGetValueAsString(out string? strValue)) { - if (!index.TryGetValueAsInteger(out int strIndex)) - ThrowAttemptedToIndexString(index); + ThrowCannotGetIndex(indexing, index); + return default; + } - char c = strValue[strIndex - 1]; - return new DreamValue(Convert.ToString(c)); - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowCannotGetIndex(DreamValue indexing, DreamValue index) { + throw new Exception($"Cannot get index {index} of {indexing}"); + } - if (indexing.TryGetValueAsDreamObject(out var dreamObject)) { - if (dreamObject != null) { - return dreamObject.OperatorIndex(index, state); - } - } + [MethodImpl(MethodImplOptions.NoInlining)] + private static void ThrowAttemptedToIndexString(DreamValue index) { + throw new Exception($"Attempted to index string with {index}"); + } - ThrowCannotGetIndex(indexing, index); - return default; - } + #endregion References - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowCannotGetIndex(DreamValue indexing, DreamValue index) { - throw new Exception($"Cannot get index {index} of {indexing}"); + public IEnumerable<(string, DreamValue)> DebugArguments() { + int i = 0; + if (_proc.ArgumentNames != null) { + while (i < _proc.ArgumentNames.Count) { + yield return (_proc.ArgumentNames[i], _localVariables[i]); + ++i; + } } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void ThrowAttemptedToIndexString(DreamValue index) { - throw new Exception($"Attempted to index string with {index}"); + // If the caller supplied excess positional arguments, they have no + // name, but the debugger should report them anyways. + while (i < ArgumentCount) { + yield return (i.ToString(), _localVariables[i]); + ++i; } + } - #endregion References - - public IEnumerable<(string, DreamValue)> DebugArguments() { - int i = 0; - if (_proc.ArgumentNames != null) { - while (i < _proc.ArgumentNames.Count) { - yield return (_proc.ArgumentNames[i], _localVariables[i]); - ++i; - } + public IEnumerable<(string, DreamValue)> DebugLocals() { + string[] names = new string[_localVariables.Length - ArgumentCount]; + int count = 0; + foreach (var info in _proc.LocalNames) { + if (info.Offset > _pc) { + break; } - // If the caller supplied excess positional arguments, they have no - // name, but the debugger should report them anyways. - while (i < ArgumentCount) { - yield return (i.ToString(), _localVariables[i]); - ++i; + + if (info.Remove is { } remove) { + count -= remove; } - } - public IEnumerable<(string, DreamValue)> DebugLocals() { - if (_proc.LocalNames is null) { - yield break; + if (info.Add is { } add) { + names[count++] = add; } + } - string[] names = new string[_localVariables.Length - ArgumentCount]; - int count = 0; - foreach (var info in _proc.LocalNames) { - if (info.Offset > _pc) { - break; - } + int i = 0, j = ArgumentCount; + while (i < count && j < _localVariables.Length) { + yield return (names[i], _localVariables[j]); + ++i; + ++j; + } + // _localVariables.Length is pool-allocated so its length may go up + // to some round power of two or similar without anything actually + // being there, so just stop after the named locals. + } - if (info.Remove is { } remove) { - count -= remove; - } + public DreamProcArguments CreateProcArguments(ReadOnlySpan values, DreamProc? proc, DMCallArgumentsType argumentsType, int argumentStackSize) { + switch (argumentsType) { + case DMCallArgumentsType.None: + return new DreamProcArguments(); + case DMCallArgumentsType.FromStack: + return new DreamProcArguments(values); + case DMCallArgumentsType.FromProcArguments: + return new DreamProcArguments(GetArguments()); + case DMCallArgumentsType.FromStackKeyed: { + if (argumentStackSize % 2 != 0) + throw new ArgumentException("Argument stack size must be even", nameof(argumentStackSize)); + if (proc == null) + throw new Exception("Cannot use named arguments here"); + + // new /mutable_appearance(...) always uses /image/New()'s arguments, despite any overrides + if (proc.OwningType == Proc.ObjectTree.MutableAppearance && proc.Name == "New") + proc = Proc.DreamManager.ImageConstructor; + + var argumentCount = argumentStackSize / 2; + var arguments = new DreamValue[Math.Max(argumentCount, proc.ArgumentNames.Count)]; + var skippingArg = false; + var isImageConstructor = proc == Proc.DreamManager.ImageConstructor || + proc == Proc.DreamManager.ImageFactoryProc; + + Array.Fill(arguments, DreamValue.Null); + for (int i = 0; i < argumentCount; i++) { + var key = values[i*2]; + var value = values[i*2+1]; + + if (key.IsNull) { + // image() or new /image() will skip the loc arg if the second arg is a string + // Really don't like this but it's BYOND behavior + // Note that the way we're doing it leads to different argument placement when there are no named args + // Hopefully nothing depends on that though + // TODO: We aim to do sanity improvements in the future, yea? Big one here + if (isImageConstructor && i == 1 && value.Type == DreamValue.DreamValueType.String) + skippingArg = true; + + arguments[skippingArg ? i + 1 : i] = value; + } else { + string argumentName = key.MustGetValueAsString(); + int argumentIndex = proc.ArgumentNames.IndexOf(argumentName); + if (argumentIndex == -1) + throw new Exception($"{proc} has no argument named {argumentName}"); - if (info.Add is { } add) { - names[count++] = add; + arguments[argumentIndex] = value; + } } - } - int i = 0, j = ArgumentCount; - while (i < count && j < _localVariables.Length) { - yield return (names[i], _localVariables[j]); - ++i; - ++j; + return new DreamProcArguments(arguments); } - // _localVariables.Length is pool-allocated so its length may go up - // to some round power of two or similar without anything actually - // being there, so just stop after the named locals. - } - - public DreamProcArguments CreateProcArguments(ReadOnlySpan values, DreamProc? proc, DMCallArgumentsType argumentsType, int argumentStackSize) { - switch (argumentsType) { - case DMCallArgumentsType.None: - return new DreamProcArguments(); - case DMCallArgumentsType.FromStack: - return new DreamProcArguments(values); - case DMCallArgumentsType.FromProcArguments: - return new DreamProcArguments(GetArguments()); - case DMCallArgumentsType.FromStackKeyed: { - if (argumentStackSize % 2 != 0) - throw new ArgumentException("Argument stack size must be even", nameof(argumentStackSize)); - if (proc == null) - throw new Exception("Cannot use named arguments here"); - - // new /mutable_appearance(...) always uses /image/New()'s arguments, despite any overrides - if (proc.OwningType == Proc.ObjectTree.MutableAppearance && proc.Name == "New") - proc = Proc.DreamManager.ImageConstructor; - - var argumentCount = argumentStackSize / 2; - var arguments = new DreamValue[Math.Max(argumentCount, proc.ArgumentNames.Count)]; - var skippingArg = false; - var isImageConstructor = proc == Proc.DreamManager.ImageConstructor || - proc == Proc.DreamManager.ImageFactoryProc; - - Array.Fill(arguments, DreamValue.Null); - for (int i = 0; i < argumentCount; i++) { - var key = values[i*2]; - var value = values[i*2+1]; - - if (key.IsNull) { - // image() or new /image() will skip the loc arg if the second arg is a string - // Really don't like this but it's BYOND behavior - // Note that the way we're doing it leads to different argument placement when there are no named args - // Hopefully nothing depends on that though - // TODO: We aim to do sanity improvements in the future, yea? Big one here - if (isImageConstructor && i == 1 && value.Type == DreamValue.DreamValueType.String) - skippingArg = true; - - arguments[skippingArg ? i + 1 : i] = value; - } else { - string argumentName = key.MustGetValueAsString(); - int argumentIndex = proc.ArgumentNames.IndexOf(argumentName); - if (argumentIndex == -1) - throw new Exception($"{proc} has no argument named {argumentName}"); - - arguments[argumentIndex] = value; - } + case DMCallArgumentsType.FromArgumentList: { + if (proc == null) + throw new Exception("Cannot use an arglist here"); + if (!values[0].TryGetValueAsDreamList(out var argList)) + return new DreamProcArguments(); // Using a non-list gives you no arguments + + // new /mutable_appearance(...) always uses /image/New()'s arguments, despite any overrides + if (proc.OwningType == Proc.ObjectTree.MutableAppearance && proc.Name == "New") + proc = Proc.DreamManager.ImageConstructor; + + var listValues = argList.GetValues(); + var arguments = new DreamValue[Math.Max(listValues.Count, proc.ArgumentNames.Count)]; + var skippingArg = false; + var isImageConstructor = proc == Proc.DreamManager.ImageConstructor || + proc == Proc.DreamManager.ImageFactoryProc; + + Array.Fill(arguments, DreamValue.Null); + for (int i = 0; i < listValues.Count; i++) { + var value = listValues[i]; + + if (argList.ContainsKey(value)) { //Named argument + if (!value.TryGetValueAsString(out var argumentName)) + throw new Exception("List contains a non-string key, and cannot be used as an arglist"); + + int argumentIndex = proc.ArgumentNames.IndexOf(argumentName); + if (argumentIndex == -1) + throw new Exception($"{proc} has no argument named {argumentName}"); + + arguments[argumentIndex] = argList.GetValue(value); + } else { //Ordered argument + // image() or new /image() will skip the loc arg if the second arg is a string + // Really don't like this but it's BYOND behavior + // Note that the way we're doing it leads to different argument placement when there are no named args + // Hopefully nothing depends on that though + if (isImageConstructor && i == 1 && value.Type == DreamValue.DreamValueType.String) + skippingArg = true; + + // TODO: Verify ordered args precede all named args + arguments[skippingArg ? i + 1 : i] = value; } - - return new DreamProcArguments(arguments); } - case DMCallArgumentsType.FromArgumentList: { - if (proc == null) - throw new Exception("Cannot use an arglist here"); - if (!values[0].TryGetValueAsDreamList(out var argList)) - return new DreamProcArguments(); // Using a non-list gives you no arguments - - // new /mutable_appearance(...) always uses /image/New()'s arguments, despite any overrides - if (proc.OwningType == Proc.ObjectTree.MutableAppearance && proc.Name == "New") - proc = Proc.DreamManager.ImageConstructor; - - var listValues = argList.GetValues(); - var arguments = new DreamValue[Math.Max(listValues.Count, proc.ArgumentNames.Count)]; - var skippingArg = false; - var isImageConstructor = proc == Proc.DreamManager.ImageConstructor || - proc == Proc.DreamManager.ImageFactoryProc; - - Array.Fill(arguments, DreamValue.Null); - for (int i = 0; i < listValues.Count; i++) { - var value = listValues[i]; - - if (argList.ContainsKey(value)) { //Named argument - if (!value.TryGetValueAsString(out var argumentName)) - throw new Exception("List contains a non-string key, and cannot be used as an arglist"); - - int argumentIndex = proc.ArgumentNames.IndexOf(argumentName); - if (argumentIndex == -1) - throw new Exception($"{proc} has no argument named {argumentName}"); - - arguments[argumentIndex] = argList.GetValue(value); - } else { //Ordered argument - // image() or new /image() will skip the loc arg if the second arg is a string - // Really don't like this but it's BYOND behavior - // Note that the way we're doing it leads to different argument placement when there are no named args - // Hopefully nothing depends on that though - if (isImageConstructor && i == 1 && value.Type == DreamValue.DreamValueType.String) - skippingArg = true; - - // TODO: Verify ordered args precede all named args - arguments[skippingArg ? i + 1 : i] = value; - } - } - return new DreamProcArguments(arguments); - } - default: - throw new Exception($"Invalid arguments type {argumentsType}"); + return new DreamProcArguments(arguments); } + default: + throw new Exception($"Invalid arguments type {argumentsType}"); } + } - public (DreamValue[]?, Dictionary?) CollectProcArguments(ReadOnlySpan values, DMCallArgumentsType argumentsType, int argumentStackSize) { - switch (argumentsType) { - case DMCallArgumentsType.None: - return (Array.Empty(), null); - case DMCallArgumentsType.FromStack: - return (values.ToArray(), null); - case DMCallArgumentsType.FromProcArguments: - return (GetArguments().ToArray(), null); - case DMCallArgumentsType.FromStackKeyed: { - if (argumentStackSize % 2 != 0) - throw new ArgumentException("Argument stack size must be even", nameof(argumentStackSize)); - - var argumentCount = argumentStackSize / 2; - var arguments = new Dictionary(argumentCount); - - for (int i = 0; i < argumentCount; i++) { - var key = values[i*2]; - var value = values[i*2+1]; - - if (key.IsNull) { - arguments[new(i + 1)] = value; - } else { - string argumentName = key.MustGetValueAsString(); - - arguments[new(argumentName)] = value; - } - } + public (DreamValue[]?, Dictionary?) CollectProcArguments(ReadOnlySpan values, DMCallArgumentsType argumentsType, int argumentStackSize) { + switch (argumentsType) { + case DMCallArgumentsType.None: + return (Array.Empty(), null); + case DMCallArgumentsType.FromStack: + return (values.ToArray(), null); + case DMCallArgumentsType.FromProcArguments: + return (GetArguments().ToArray(), null); + case DMCallArgumentsType.FromStackKeyed: { + if (argumentStackSize % 2 != 0) + throw new ArgumentException("Argument stack size must be even", nameof(argumentStackSize)); + + var argumentCount = argumentStackSize / 2; + var arguments = new Dictionary(argumentCount); + + for (int i = 0; i < argumentCount; i++) { + var key = values[i*2]; + var value = values[i*2+1]; + + if (key.IsNull) { + arguments[new(i + 1)] = value; + } else { + string argumentName = key.MustGetValueAsString(); - return (null, arguments); + arguments[new(argumentName)] = value; + } } - case DMCallArgumentsType.FromArgumentList: { - if (!values[0].TryGetValueAsDreamList(out var argList)) - return (Array.Empty(), null); // Using a non-list gives you no arguments - var listValues = argList.GetValues(); - var arguments = new Dictionary(); + return (null, arguments); + } + case DMCallArgumentsType.FromArgumentList: { + if (!values[0].TryGetValueAsDreamList(out var argList)) + return (Array.Empty(), null); // Using a non-list gives you no arguments + + var listValues = argList.GetValues(); + var arguments = new Dictionary(); - for (int i = 0; i < listValues.Count; i++) { - var value = listValues[i]; + for (int i = 0; i < listValues.Count; i++) { + var value = listValues[i]; - if (argList.ContainsKey(value)) { //Named argument - if (!value.TryGetValueAsString(out var argumentName)) - throw new Exception("List contains a non-string key, and cannot be used as an arglist"); + if (argList.ContainsKey(value)) { //Named argument + if (!value.TryGetValueAsString(out var argumentName)) + throw new Exception("List contains a non-string key, and cannot be used as an arglist"); - arguments[new(argumentName)] = argList.GetValue(value); - } else { //Ordered argument - // TODO: Verify ordered args precede all named args - arguments[new(i + 1)] = value; - } + arguments[new(argumentName)] = argList.GetValue(value); + } else { //Ordered argument + // TODO: Verify ordered args precede all named args + arguments[new(i + 1)] = value; } - - return (null, arguments); } - default: - throw new Exception($"Invalid arguments type {argumentsType}"); + + return (null, arguments); } + default: + throw new Exception($"Invalid arguments type {argumentsType}"); } } } diff --git a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs index 56339a2316..8f747f90bf 100644 --- a/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs +++ b/OpenDreamRuntime/Procs/DebugAdapter/DreamDebugManager.cs @@ -238,7 +238,7 @@ public ProcStatus HandleBreakpoint(DMProcState state) { // Execute the original opcode unsafe { - return DMProcState.OpcodeHandlers[breakpoint.OriginalOpcode](state); + return DMProcState.OpcodeHandlersTable[breakpoint.OriginalOpcode](state); } } diff --git a/OpenDreamRuntime/Procs/DreamEnumerators.cs b/OpenDreamRuntime/Procs/DreamEnumerators.cs index 6cd5583e31..b9da196424 100644 --- a/OpenDreamRuntime/Procs/DreamEnumerators.cs +++ b/OpenDreamRuntime/Procs/DreamEnumerators.cs @@ -11,21 +11,13 @@ public interface IDreamValueEnumerator { /// Enumerates a range of numbers with a given step /// for (var/i in 1 to 10 step 2) /// -internal sealed class DreamValueRangeEnumerator : IDreamValueEnumerator { - private float _current; - private readonly float _end; - private readonly float _step; - - public DreamValueRangeEnumerator(float rangeStart, float rangeEnd, float step) { - _current = rangeStart - step; - _end = rangeEnd; - _step = step; - } +internal sealed class DreamValueRangeEnumerator(float rangeStart, float rangeEnd, float step) : IDreamValueEnumerator { + private float _current = rangeStart - step; public bool Enumerate(DMProcState state, DreamReference? reference) { - _current += _step; + _current += step; - bool successful = (_step > 0) ? _current <= _end : _current >= _end; + bool successful = (step > 0) ? _current <= rangeEnd : _current >= rangeEnd; if (successful && reference != null) // Only assign if it was successful state.AssignReference(reference.Value, new DreamValue(_current)); @@ -36,14 +28,8 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { /// /// Enumerates over an IEnumerable of DreamObjects, possibly filtering for a certain type /// -internal sealed class DreamObjectEnumerator : IDreamValueEnumerator { - private readonly IEnumerator _dreamObjectEnumerator; - private readonly TreeEntry? _filterType; - - public DreamObjectEnumerator(IEnumerable dreamObjects, TreeEntry? filterType = null) { - _dreamObjectEnumerator = dreamObjects.GetEnumerator(); - _filterType = filterType; - } +internal sealed class DreamObjectEnumerator(IEnumerable dreamObjects, TreeEntry? filterType = null) : IDreamValueEnumerator { + private readonly IEnumerator _dreamObjectEnumerator = dreamObjects.GetEnumerator(); public bool Enumerate(DMProcState state, DreamReference? reference) { bool success = _dreamObjectEnumerator.MoveNext(); @@ -51,8 +37,8 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { while(success && _dreamObjectEnumerator.Current.Deleted) //skip over deleted success = _dreamObjectEnumerator.MoveNext(); - if (_filterType != null) { - while (success && (_dreamObjectEnumerator.Current.Deleted || !_dreamObjectEnumerator.Current.IsSubtypeOf(_filterType))) { + if (filterType != null) { + while (success && (_dreamObjectEnumerator.Current.Deleted || !_dreamObjectEnumerator.Current.IsSubtypeOf(filterType))) { success = _dreamObjectEnumerator.MoveNext(); } } @@ -68,20 +54,15 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { /// Enumerates over an array of DreamValues /// for (var/i in list(1, 2, 3)) /// -internal sealed class DreamValueArrayEnumerator : IDreamValueEnumerator { - private readonly DreamValue[] _dreamValueArray; +internal sealed class DreamValueArrayEnumerator(DreamValue[] dreamValueArray) : IDreamValueEnumerator { private int _current = -1; - public DreamValueArrayEnumerator(DreamValue[] dreamValueArray) { - _dreamValueArray = dreamValueArray; - } - public bool Enumerate(DMProcState state, DreamReference? reference) { _current++; - bool success = _current < _dreamValueArray.Length; + bool success = _current < dreamValueArray.Length; if (reference != null) - state.AssignReference(reference.Value, success ? _dreamValueArray[_current] : DreamValue.Null); // Assign regardless of success + state.AssignReference(reference.Value, success ? dreamValueArray[_current] : DreamValue.Null); // Assign regardless of success return success; } } @@ -90,27 +71,20 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { /// Enumerates over an array of DreamValues, filtering for a certain type /// for (var/obj/item/I in contents) /// -internal sealed class FilteredDreamValueArrayEnumerator : IDreamValueEnumerator { - private readonly DreamValue[] _dreamValueArray; - private readonly TreeEntry _filterType; +internal sealed class FilteredDreamValueArrayEnumerator(DreamValue[] dreamValueArray, TreeEntry filterType) : IDreamValueEnumerator { private int _current = -1; - public FilteredDreamValueArrayEnumerator(DreamValue[] dreamValueArray, TreeEntry filterType) { - _dreamValueArray = dreamValueArray; - _filterType = filterType; - } - public bool Enumerate(DMProcState state, DreamReference? reference) { do { _current++; - if (_current >= _dreamValueArray.Length) { + if (_current >= dreamValueArray.Length) { if (reference != null) state.AssignReference(reference.Value, DreamValue.Null); return false; } - DreamValue value = _dreamValueArray[_current]; - if (value.TryGetValueAsDreamObject(out var dreamObject) && (dreamObject?.IsSubtypeOf(_filterType) ?? false)) { + DreamValue value = dreamValueArray[_current]; + if (value.TryGetValueAsDreamObject(out var dreamObject) && (dreamObject?.IsSubtypeOf(filterType) ?? false)) { if (reference != null) state.AssignReference(reference.Value, value); return true; @@ -123,12 +97,8 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { /// Enumerates over all atoms in the world, possibly filtering for a certain type /// for (var/obj/item/I in world) /// -internal sealed class WorldContentsEnumerator : IDreamValueEnumerator { - private readonly IEnumerator _enumerator; - - public WorldContentsEnumerator(AtomManager atomManager, TreeEntry? filterType) { - _enumerator = atomManager.EnumerateAtoms(filterType).GetEnumerator(); - } +internal sealed class WorldContentsEnumerator(AtomManager atomManager, TreeEntry? filterType) : IDreamValueEnumerator { + private readonly IEnumerator _enumerator = atomManager.EnumerateAtoms(filterType).GetEnumerator(); public bool Enumerate(DMProcState state, DreamReference? reference) { bool success = _enumerator.MoveNext();