Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement as syntax for multiple base types (mob|obj|turf) #1917

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion DMCompiler/Bytecode/DreamProcOpcode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
28 changes: 16 additions & 12 deletions DMCompiler/DM/Builders/DMProcBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -602,32 +602,36 @@ 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;
} 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.
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");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want this print here?

}

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();
Expand Down
4 changes: 4 additions & 0 deletions DMCompiler/DM/DMObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,8 @@ public DMValueType GetDMValueType() {

return DMValueType.Anything;
}

public DreamPath[] GetDMAncestors() {

}
Comment on lines +233 to +236
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

}
10 changes: 10 additions & 0 deletions DMCompiler/DM/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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++);
Expand Down Expand Up @@ -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);
}
Expand Down
20 changes: 20 additions & 0 deletions DMCompiler/Optimizer/AnnotatedByteCodeWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,26 @@ public void WriteFilterId(int filterTypeId, DreamPath filterPath, Location locat
_annotatedBytecode[^1].AddArg(new AnnotatedBytecodeFilter(filterTypeId, filterPath, location));
}

/// <summary>
/// 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.
/// </summary>
/// <param name="filterTypeId">The type ID of the filter</param>
/// /// <param name="filterTypes">The base types of the filter</param>
/// <param name="location">The location of the filter in the source code</param>
///
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));
}

/// <summary>
/// Write a list size, restricted to non-negative integers
/// </summary>
Expand Down
29 changes: 29 additions & 0 deletions DMCompiler/Optimizer/AnnotatedBytecode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions DMCompiler/Optimizer/AnnotatedBytecodeSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
4 changes: 4 additions & 0 deletions OpenDreamRuntime/Objects/DreamObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 5 additions & 0 deletions OpenDreamRuntime/Objects/DreamObjectDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
5 changes: 5 additions & 0 deletions OpenDreamRuntime/Objects/DreamObjectTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
40 changes: 40 additions & 0 deletions OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,36 @@ private static IDreamValueEnumerator GetContentsEnumerator(DreamObjectTree objec
return new DreamValueArrayEnumerator(Array.Empty<DreamValue>());
}

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<DreamValue>());

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<DreamValue>());
}

public static ProcStatus CreateListEnumerator(DMProcState state) {
var enumeratorId = state.ReadInt();
var enumerator = GetContentsEnumerator(state.Proc.ObjectTree, state.Proc.AtomManager, state.Pop(), null);
Expand All @@ -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();
Expand Down
1 change: 1 addition & 0 deletions OpenDreamRuntime/Procs/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
35 changes: 34 additions & 1 deletion OpenDreamRuntime/Procs/DreamEnumerators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public bool Enumerate(DMProcState state, DreamReference? reference) {

/// <summary>
/// Enumerates over an array of DreamValues, filtering for a certain type
/// <code>for (var/obj/item/I in contents)</code>
/// <code>for (var/I as obj|mob in contents)</code>
/// </summary>
internal sealed class FilteredDreamValueArrayEnumerator : IDreamValueEnumerator {
private readonly DreamValue[] _dreamValueArray;
Expand Down Expand Up @@ -115,6 +115,39 @@ public bool Enumerate(DMProcState state, DreamReference? reference) {
}
}

/// <summary>
/// Enumerates over an array of DreamValues, filtering for a certain type
/// <code>for (var/I as obj|mob in contents)</code>
/// </summary>
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);
}
}

/// <summary>
/// Enumerates over all atoms in the world, possibly filtering for a certain type
/// <code>for (var/obj/item/I in world)</code>
Expand Down
7 changes: 7 additions & 0 deletions OpenDreamRuntime/Procs/ProcDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -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) {
Expand Down