diff --git a/DMCompiler/Bytecode/DreamProcOpcode.cs b/DMCompiler/Bytecode/DreamProcOpcode.cs index e73e513fe8..59ab997b4b 100644 --- a/DMCompiler/Bytecode/DreamProcOpcode.cs +++ b/DMCompiler/Bytecode/DreamProcOpcode.cs @@ -137,7 +137,8 @@ public enum DreamProcOpcode : byte { CreateFilteredListEnumerator = 0x41, [OpcodeMetadata(-1)] Power = 0x42, - //0x43, + [OpcodeMetadata(-1, OpcodeArgType.EnumeratorId, OpcodeArgType.FilterId)] + CreateFilteredBaseTypesListEnumerator = 0x43, //0x44 [OpcodeMetadata(-3, OpcodeArgType.TypeId)] Prompt = 0x45, diff --git a/DMCompiler/DM/Builders/DMProcBuilder.cs b/DMCompiler/DM/Builders/DMProcBuilder.cs index a52cd9265f..2ed16a4199 100644 --- a/DMCompiler/DM/Builders/DMProcBuilder.cs +++ b/DMCompiler/DM/Builders/DMProcBuilder.cs @@ -602,8 +602,10 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D lValue = null; } + list.EmitPushValue(dmObject, proc); // Depending on the var's type and possibly a given "as [types]", an implicit istype() check is performed DreamPath? implicitTypeCheck = null; + bool alreadyCreatedEnumerator = false; // Keep track of if we did CreateFilteredBaseTypesListEnumerator already if (dmTypes == null) { // No "as" means the var's type will be used implicitTypeCheck = lValue?.Path; @@ -611,23 +613,25 @@ public void ProcessStatementForList(DMExpression list, DMExpression outputVar, D // "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. - DMCompiler.UnimplementedWarning(outputVar.Location, - $"As type {dmTypes} in for loops is unimplemented. No type check will be performed."); + // "as anything" performs no check. Other than that, all that's left are type assignments like `as mob|obj|area`. + proc.CreateFilteredBaseTypesListEnumerator((byte) dmTypes.Value.Type, dmTypes.Value.Type); + alreadyCreatedEnumerator = true; + DMCompiler.VerbosePrint($"Created CreateFilteredBaseTypesListEnumerator"); } - list.EmitPushValue(dmObject, proc); - if (implicitTypeCheck != null) { - if (DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) { - // Create an enumerator that will do the implicit istype() for us - proc.CreateFilteredListEnumerator(filterTypeId, implicitTypeCheck.Value); + if (!alreadyCreatedEnumerator) { + if (implicitTypeCheck != null) { + if (DMObjectTree.TryGetTypeId(implicitTypeCheck.Value, out var filterTypeId)) { + // Create an enumerator that will do the implicit istype() for us + proc.CreateFilteredListEnumerator(filterTypeId, implicitTypeCheck.Value); + } else { + DMCompiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location, + $"Cannot filter enumeration by type {implicitTypeCheck.Value}, it does not exist"); + proc.CreateListEnumerator(); + } } else { - DMCompiler.Emit(WarningCode.ItemDoesntExist, outputVar.Location, - $"Cannot filter enumeration by type {implicitTypeCheck.Value}, it does not exist"); proc.CreateListEnumerator(); } - } else { - proc.CreateListEnumerator(); } proc.StartScope(); diff --git a/DMCompiler/DM/DMObject.cs b/DMCompiler/DM/DMObject.cs index ea65433a88..52172642b8 100644 --- a/DMCompiler/DM/DMObject.cs +++ b/DMCompiler/DM/DMObject.cs @@ -230,4 +230,8 @@ public DMValueType GetDMValueType() { return DMValueType.Anything; } + + public DreamPath[] GetDMAncestors() { + + } } diff --git a/DMCompiler/DM/DMProc.cs b/DMCompiler/DM/DMProc.cs index 8c1e5be54d..3a18443691 100644 --- a/DMCompiler/DM/DMProc.cs +++ b/DMCompiler/DM/DMProc.cs @@ -372,6 +372,12 @@ public void CreateFilteredListEnumerator(int filterTypeId, DreamPath filterType) WriteFilterID(filterTypeId, filterType); } + public void CreateFilteredBaseTypesListEnumerator(byte filterTypeId, DMValueType filterType) { + WriteOpcode(DreamProcOpcode.CreateFilteredBaseTypesListEnumerator); + WriteEnumeratorId(_enumeratorIdCounter++); + WriteFilterTypeID(filterTypeId, filterType); + } + public void CreateTypeEnumerator() { WriteOpcode(DreamProcOpcode.CreateTypeEnumerator); WriteEnumeratorId(_enumeratorIdCounter++); @@ -1115,6 +1121,10 @@ private void WriteFilterID(int filterId, DreamPath filter) { AnnotatedBytecode.WriteFilterId(filterId, filter, _writerLocation); } + private void WriteFilterTypeID(byte filterId, DMValueType filter) { + AnnotatedBytecode.WriteFilterTypeId(filterId, filter, _writerLocation); + } + private void WriteStackDelta(int delta) { AnnotatedBytecode.WriteStackDelta(delta, _writerLocation); } diff --git a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs index 9009df1ecf..903b8aa67f 100644 --- a/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs +++ b/DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs @@ -152,6 +152,26 @@ public void WriteFilterId(int filterTypeId, DreamPath filterPath, Location locat _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeFilter(filterTypeId, filterPath, location)); } + /// + /// Write a filter. Filters are stored as reference IDs in the raw bytecode, which refer + /// to a string in the string table containing the datum path of the filter. + /// + /// The type ID of the filter + /// /// The base types of the filter + /// The location of the filter in the source code + /// + public void WriteFilterTypeId(byte filterTypeId, DMValueType filterTypes, Location location) { + _location = location; + + if (_requiredArgs.Count == 0 || _requiredArgs.Peek() != OpcodeArgType.FilterId) { + DMCompiler.ForcedError(location, "Expected filter argument"); + } + + _requiredArgs.Pop(); + + _annotatedBytecode[^1].AddArg(new AnnotatedBytecodeTypeFilter(filterTypeId, filterTypes, location)); + } + /// /// Write a list size, restricted to non-negative integers /// diff --git a/DMCompiler/Optimizer/AnnotatedBytecode.cs b/DMCompiler/Optimizer/AnnotatedBytecode.cs index 35dbd2fab0..4faac4159a 100644 --- a/DMCompiler/Optimizer/AnnotatedBytecode.cs +++ b/DMCompiler/Optimizer/AnnotatedBytecode.cs @@ -574,6 +574,35 @@ public Location GetLocation() { } } +internal sealed class AnnotatedBytecodeTypeFilter : IAnnotatedBytecode { + public DMValueType FilterPath; + + public int FilterTypeId; + public Location Location; + + public AnnotatedBytecodeTypeFilter(int filterTypeId, DMValueType filterPath, Location location) { + FilterTypeId = filterTypeId; + FilterPath = filterPath; + Location = location; + } + + public void AddArg(IAnnotatedBytecode arg) { + DMCompiler.ForcedError(Location, "Cannot add args to a filter"); + } + + public void SetLocation(IAnnotatedBytecode loc) { + Location = loc.GetLocation(); + } + + public void SetLocation(Location loc) { + Location = loc; + } + + public Location GetLocation() { + return Location; + } +} + internal sealed class AnnotatedBytecodeReference : IAnnotatedBytecode { public int Index; public Location Location; diff --git a/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs b/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs index 9d92355fae..cb1dda3412 100644 --- a/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs +++ b/DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs @@ -140,6 +140,9 @@ private void SerializeInstruction(AnnotatedBytecodeInstruction instruction) { case AnnotatedBytecodeTypeId annotatedBytecodeTypeId: _bytecodeWriter.Write(annotatedBytecodeTypeId.TypeId); break; + case AnnotatedBytecodeTypeFilter annotatedBytecodeTypeFilter: + _bytecodeWriter.Write(annotatedBytecodeTypeFilter.FilterTypeId); + break; default: throw new ArgumentOutOfRangeException(); } diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index 912ba49781..7c81f8d930 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -120,6 +120,10 @@ public bool IsSubtypeOf(TreeEntry ancestor) { return ObjectDefinition.IsSubtypeOf(ancestor); } + public bool IsSubtypeOf(byte ancestors) { + return ObjectDefinition.IsSubtypeOf(ancestors); + } + #region Variables public virtual bool IsSaved(string name) { return ObjectDefinition.Variables.ContainsKey(name) diff --git a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs index 670299e238..eebd3a104c 100644 --- a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs +++ b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs @@ -197,4 +197,9 @@ public bool TryGetVariable(string varName, out DreamValue value) { public bool IsSubtypeOf(TreeEntry ancestor) { return TreeEntry.IsSubtypeOf(ancestor); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSubtypeOf(byte ancestors) { + return TreeEntry.IsSubtypeOf(ancestors); + } } diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index ce86bb4c33..bf351c0ac6 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -516,6 +516,11 @@ public bool IsSubtypeOf(TreeEntry ancestor) { return (TreeIndex - ancestor.TreeIndex) <= ancestor.ChildCount; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSubtypeOf(byte ancestors) { + return true; + } + public override string ToString() { return Path; } diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 34ec285840..c143fdfe25 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -107,6 +107,36 @@ private static IDreamValueEnumerator GetContentsEnumerator(DreamObjectTree objec return new DreamValueArrayEnumerator(Array.Empty()); } + private static IDreamValueEnumerator GetContentsEnumeratorAeiou(DreamObjectTree objectTree, AtomManager atomManager, DreamValue value, byte? filterType) { + if (!value.TryGetValueAsDreamList(out var list)) { + if (value.TryGetValueAsDreamObject(out var dreamObject)) { + if (dreamObject == null) + return new DreamValueArrayEnumerator(Array.Empty()); + + if (dreamObject is DreamObjectAtom) { + list = dreamObject.GetVariable("contents").MustGetValueAsDreamList(); + } else if (dreamObject is DreamObjectWorld) { + return new WorldContentsEnumerator(atomManager, filterType); + } + } + } + + if (list != null) { + // world.contents has its own special enumerator to prevent the huge copy + if (list is WorldContentsList) + return new WorldContentsEnumerator(atomManager, filterType); + + var values = list.GetValues().ToArray(); + + return filterType == null + ? new DreamValueArrayEnumerator(values) + : new FilteredDreamValueArrayEnumeratorAeiou(values, (byte) filterType); + } + + // BYOND ignores all floats, strings, types, etc. here and just doesn't run the loop. + return new DreamValueArrayEnumerator(Array.Empty()); + } + public static ProcStatus CreateListEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); var enumerator = GetContentsEnumerator(state.Proc.ObjectTree, state.Proc.AtomManager, state.Pop(), null); @@ -125,6 +155,16 @@ public static ProcStatus CreateFilteredListEnumerator(DMProcState state) { return ProcStatus.Continue; } + public static ProcStatus CreateFilteredBaseTypesListEnumerator(DMProcState state) { + var enumeratorId = state.ReadInt(); + var filterTypeId = state.ReadByte(); + var filterType = state.Proc.ObjectTree.GetTreeEntry(filterTypeId); + var enumerator = GetContentsEnumeratorAeiou(state.Proc.ObjectTree, state.Proc.AtomManager, state.Pop(), filterType); + + state.Enumerators[enumeratorId] = enumerator; + return ProcStatus.Continue; + } + public static ProcStatus CreateTypeEnumerator(DMProcState state) { var enumeratorId = state.ReadInt(); var typeValue = state.Pop(); diff --git a/OpenDreamRuntime/Procs/DMProc.cs b/OpenDreamRuntime/Procs/DMProc.cs index 4b83a2477b..825a665418 100644 --- a/OpenDreamRuntime/Procs/DMProc.cs +++ b/OpenDreamRuntime/Procs/DMProc.cs @@ -242,6 +242,7 @@ public sealed class DMProcState : ProcState { {DreamProcOpcode.OutputControl, DMOpcodeHandlers.OutputControl}, {DreamProcOpcode.BitShiftRight, DMOpcodeHandlers.BitShiftRight}, {DreamProcOpcode.CreateFilteredListEnumerator, DMOpcodeHandlers.CreateFilteredListEnumerator}, + {DreamProcOpcode.CreateFilteredBaseTypesListEnumerator, DMOpcodeHandlers.CreateFilteredBaseTypesListEnumerator}, {DreamProcOpcode.Power, DMOpcodeHandlers.Power}, {DreamProcOpcode.Prompt, DMOpcodeHandlers.Prompt}, {DreamProcOpcode.Ftp, DMOpcodeHandlers.Ftp}, diff --git a/OpenDreamRuntime/Procs/DreamEnumerators.cs b/OpenDreamRuntime/Procs/DreamEnumerators.cs index b954ea507b..c47d4ec9dd 100644 --- a/OpenDreamRuntime/Procs/DreamEnumerators.cs +++ b/OpenDreamRuntime/Procs/DreamEnumerators.cs @@ -84,7 +84,7 @@ 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) +/// for (var/I as obj|mob in contents) /// internal sealed class FilteredDreamValueArrayEnumerator : IDreamValueEnumerator { private readonly DreamValue[] _dreamValueArray; @@ -115,6 +115,39 @@ public bool Enumerate(DMProcState state, DreamReference? reference) { } } +/// +/// Enumerates over an array of DreamValues, filtering for a certain type +/// for (var/I as obj|mob in contents) +/// +internal sealed class FilteredDreamValueArrayEnumeratorAeiou : IDreamValueEnumerator { + private readonly DreamValue[] _dreamValueArray; + private readonly byte _filterType; + private int _current = -1; + + public FilteredDreamValueArrayEnumeratorAeiou(DreamValue[] dreamValueArray, byte filterType) { + _dreamValueArray = dreamValueArray; + _filterType = filterType; + } + + public bool Enumerate(DMProcState state, DreamReference? reference) { + do { + _current++; + 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)) { + if (reference != null) + state.AssignReference(reference.Value, value); + return true; + } + } while (true); + } +} + /// /// Enumerates over all atoms in the world, possibly filtering for a certain type /// for (var/obj/item/I in world) diff --git a/OpenDreamRuntime/Procs/ProcDecoder.cs b/OpenDreamRuntime/Procs/ProcDecoder.cs index f5725b082a..dc1eaf3a6d 100644 --- a/OpenDreamRuntime/Procs/ProcDecoder.cs +++ b/OpenDreamRuntime/Procs/ProcDecoder.cs @@ -162,6 +162,7 @@ public ITuple DecodeInstruction() { return (opcode, ReadInt(), ReadReference(), ReadInt()); case DreamProcOpcode.CreateFilteredListEnumerator: + case DreamProcOpcode.CreateFilteredBaseTypesListEnumerator: case DreamProcOpcode.EnumerateNoAssign: return (opcode, ReadInt(), ReadInt()); @@ -289,6 +290,12 @@ or DreamProcOpcode.JumpIfTrueReference text.Append(getTypePath(type)); break; + case (DreamProcOpcode.CreateFilteredBaseTypesListEnumerator, int enumeratorId, int type): + text.Append(enumeratorId); + text.Append(' '); + text.Append(getTypePath(type)); + break; + case (DreamProcOpcode.CreateListNRefs or DreamProcOpcode.PushNRefs, DMReference[] refs): { foreach (var reference in refs) {