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 type filtering on for-loops with as mob|turf|etc #2228

Merged
merged 4 commits into from
Feb 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 49 additions & 21 deletions DMCompiler/DM/Builders/DMProcBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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);
Expand Down
5 changes: 0 additions & 5 deletions DMCompiler/DM/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions DMCompiler/DM/Expressions/LValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ public virtual void EmitPushInitial(ExpressionContext ctx) {
}
}

/// <summary>
/// Used when there was an error regarding L-Values
/// </summary>
/// <remarks>Emit an error code before creating!</remarks>
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,
Expand Down
Loading
Loading