From 7e9d0c5406b54363706387c6b90792d38e266376 Mon Sep 17 00:00:00 2001 From: wixoaGit Date: Sun, 23 Feb 2025 20:26:41 -0500 Subject: [PATCH] Implement type filtering on for-loops with `as mob|turf|etc` --- DMCompiler/DM/Builders/DMProcBuilder.cs | 70 ++++++++---- DMCompiler/DM/DMProc.cs | 5 - DMCompiler/DM/Expressions/LValue.cs | 13 +++ OpenDreamRuntime/Procs/DMOpcodeHandlers.cs | 120 +++++++++++---------- OpenDreamRuntime/Procs/DreamEnumerators.cs | 64 +++-------- 5 files changed, 142 insertions(+), 130 deletions(-) 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/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();