Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Ruzihm/OpenDream into ruzihm-brea…
Browse files Browse the repository at this point in the history
…koutcompiler
  • Loading branch information
Ruzihm committed Feb 24, 2025
2 parents f2709d5 + b7aaab6 commit 05463bd
Show file tree
Hide file tree
Showing 7 changed files with 1,101 additions and 1,084 deletions.
70 changes: 49 additions & 21 deletions DMCompiler/DM/Builders/DMProcBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -604,37 +604,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 @@ -645,8 +638,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 @@ -581,11 +581,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 @@ -33,6 +33,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

0 comments on commit 05463bd

Please sign in to comment.