Skip to content
Open
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
59 changes: 59 additions & 0 deletions Content.Tests/DMProject/Tests/Operators/comparison2.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/proc/comparison_reference_proc_a()
return

/proc/comparison_reference_proc_b()
return

/proc/assert_left_preserving_comparisons(label, left, right)
var/result = left < right
if (result != left)
CRASH("[label] < expected the left operand, got [result]")

result = left <= right
if (result != left)
CRASH("[label] <= expected the left operand, got [result]")

result = left > right
if (result != left)
CRASH("[label] > expected the left operand, got [result]")

result = left >= right
if (result != left)
CRASH("[label] >= expected the left operand, got [result]")

/proc/RunTest()
var/datum/datum_a = new
var/datum/datum_b = new
var/list/list_a = list(1)
var/list/list_b = list(1)
var/typepath_a = /datum
var/typepath_b = /atom
var/procpath_a = /proc/comparison_reference_proc_a
var/procpath_b = /proc/comparison_reference_proc_b
var/icon_a = icon()
var/icon_b = icon()
var/sound_a = sound()
var/sound_b = sound()
var/file_a = file("comparison_probe_a.tmp")
var/file_b = file("comparison_probe_b.tmp")

var/list/reference_names = list("datum", "list", "typepath", "procpath", "icon", "sound", "file")
var/list/reference_values = list(datum_a, list_a, typepath_a, procpath_a, icon_a, sound_a, file_a)
var/list/left_names = reference_names + list("num", "text", "null")
var/list/left_values = reference_values + list(1, "abc", null)

for (var/left_index in 1 to left_values.len)
for (var/right_index in 1 to reference_values.len)
assert_left_preserving_comparisons(
"[left_names[left_index]]_[reference_names[right_index]]",
left_values[left_index],
reference_values[right_index]
)

assert_left_preserving_comparisons("distinct datums", datum_a, datum_b)
assert_left_preserving_comparisons("distinct lists", list_a, list_b)
assert_left_preserving_comparisons("distinct typepaths", typepath_a, typepath_b)
assert_left_preserving_comparisons("distinct proc paths", procpath_a, procpath_b)
assert_left_preserving_comparisons("distinct icons", icon_a, icon_b)
assert_left_preserving_comparisons("distinct sounds", sound_a, sound_b)
assert_left_preserving_comparisons("distinct files", file_a, file_b)
9 changes: 9 additions & 0 deletions Content.Tests/DMProject/Tests/Operators/pre_decrement.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/proc/RunTest()
var/text_value = "bad"
ASSERT(--text_value == -1)

var/list/list_values = list("a","b","c")
ASSERT(--list_values[1] == -1)

var/null_value = null
ASSERT(--null_value == -1)
9 changes: 9 additions & 0 deletions Content.Tests/DMProject/Tests/Operators/pre_increment.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/proc/RunTest()
var/text_value = "bad"
ASSERT(++text_value == 1)

var/list/list_values = list("a","b","c")
ASSERT(++list_values[1] == 1)

var/null_value = null
ASSERT(++null_value == 1)
6 changes: 4 additions & 2 deletions DMCompiler/Bytecode/DreamProcOpcode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,10 @@ public enum DreamProcOpcode : byte {
ModulusModulus = 0x60,
[OpcodeMetadata(0, OpcodeArgType.Reference)]
ModulusModulusReference = 0x61,
//0x62
//0x63
[OpcodeMetadata(1, OpcodeArgType.Reference)]
PreIncrement = 0x62,
[OpcodeMetadata(1, OpcodeArgType.Reference)]
PreDecrement = 0x63,
[OpcodeMetadata(0, OpcodeArgType.Label)]
JumpIfNull = 0x64,
[OpcodeMetadata(0, OpcodeArgType.Label)]
Expand Down
10 changes: 10 additions & 0 deletions DMCompiler/DM/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1016,11 +1016,21 @@ public void Append(DMReference reference) {
WriteReference(reference);
}

public void PreIncrement(DMReference reference) {
WriteOpcode(DreamProcOpcode.PreIncrement);
WriteReference(reference);
}

public void Increment(DMReference reference) {
WriteOpcode(DreamProcOpcode.Increment);
WriteReference(reference);
}

public void PreDecrement(DMReference reference) {
WriteOpcode(DreamProcOpcode.PreDecrement);
WriteReference(reference);
}

public void Decrement(DMReference reference) {
WriteOpcode(DreamProcOpcode.Decrement);
WriteReference(reference);
Expand Down
6 changes: 2 additions & 4 deletions DMCompiler/DM/Expressions/Unary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ public override void EmitPushValue(ExpressionContext ctx) {
// ++x
internal sealed class PreIncrement(Location location, DMExpression expr) : AssignmentUnaryOp(location, expr) {
protected override void EmitOp(DMProc proc, DMReference reference) {
proc.PushFloat(1);
proc.Append(reference);
proc.PreIncrement(reference);
}
}

Expand All @@ -100,8 +99,7 @@ protected override void EmitOp(DMProc proc, DMReference reference) {
// --x
internal sealed class PreDecrement(Location location, DMExpression expr) : AssignmentUnaryOp(location, expr) {
protected override void EmitOp(DMProc proc, DMReference reference) {
proc.PushFloat(1);
proc.Remove(reference);
proc.PreDecrement(reference);
}
}

Expand Down
64 changes: 52 additions & 12 deletions OpenDreamRuntime/Procs/DMOpcodeHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -927,24 +927,30 @@ private static DreamValue AppendHelper(DMProcState state) {
}

public static ProcStatus Increment(DMProcState state) {
var reference = state.ReadReference();
using var value = state.GetReferenceValue(reference, peek: true);

//If it's not a number, it turns into 1
state.AssignReference(reference, new(value.UnsafeGetValueAsFloat() + 1));
return IncrementDecrement(state, 1, returnPrevious: true);
}

state.Push(value);
return ProcStatus.Continue;
public static ProcStatus PreIncrement(DMProcState state) {
return IncrementDecrement(state, 1, returnPrevious: false);
}

public static ProcStatus Decrement(DMProcState state) {
return IncrementDecrement(state, -1, returnPrevious: true);
}

public static ProcStatus PreDecrement(DMProcState state) {
return IncrementDecrement(state, -1, returnPrevious: false);
}

private static ProcStatus IncrementDecrement(DMProcState state, float adjustment, bool returnPrevious) {
var reference = state.ReadReference();
using var value = state.GetReferenceValue(reference, peek: true);
DreamValue result = new(value.UnsafeGetValueAsFloat() + adjustment);

//If it's not a number, it turns into -1
state.AssignReference(reference, new(value.UnsafeGetValueAsFloat() - 1));
// BYOND coerces every non-number including null to 0 for ++ and --
state.AssignReference(reference, result);

state.Push(value);
state.Push(returnPrevious ? value : result);
return ProcStatus.Continue;
}

Expand Down Expand Up @@ -1482,6 +1488,11 @@ public static ProcStatus CompareGreaterThan(DMProcState state) {
using var second = state.Pop();
using var first = state.Pop();

if (TryGetReferenceComparisonResult(first, second, out DreamValue referenceResult)) {
state.Push(referenceResult);
return ProcStatus.Continue;
}

state.Push(new DreamValue(IsGreaterThan(first, second) ? 1 : 0));
return ProcStatus.Continue;
}
Expand All @@ -1491,7 +1502,9 @@ public static ProcStatus CompareGreaterThanOrEqual(DMProcState state) {
using var first = state.Pop();
DreamValue result;

if (first.TryGetValueAsFloat(out float lhs) && lhs == 0.0 && second.IsNull) result = new DreamValue(1);
if (TryGetReferenceComparisonResult(first, second, out DreamValue referenceResult))
result = referenceResult;
else if (first.TryGetValueAsFloat(out float lhs) && lhs == 0.0 && second.IsNull) result = new DreamValue(1);
else if (first.IsNull && second.TryGetValueAsFloat(out float rhs) && rhs == 0.0) result = new DreamValue(1);
else if (first.IsNull && second.TryGetValueAsString(out var s) && s == "") result = new DreamValue(1);
else result = new DreamValue((IsEqual(first, second) || IsGreaterThan(first, second)) ? 1 : 0);
Expand All @@ -1504,6 +1517,11 @@ public static ProcStatus CompareLessThan(DMProcState state) {
using var second = state.Pop();
using var first = state.Pop();

if (TryGetReferenceComparisonResult(first, second, out DreamValue referenceResult)) {
state.Push(referenceResult);
return ProcStatus.Continue;
}

state.Push(new DreamValue(IsLessThan(first, second) ? 1 : 0));
return ProcStatus.Continue;
}
Expand All @@ -1513,7 +1531,9 @@ public static ProcStatus CompareLessThanOrEqual(DMProcState state) {
using var first = state.Pop();
DreamValue result;

if (first.TryGetValueAsFloat(out float lhs) && lhs == 0.0 && second.IsNull) result = new DreamValue(1);
if (TryGetReferenceComparisonResult(first, second, out DreamValue referenceResult))
result = referenceResult;
else if (first.TryGetValueAsFloat(out float lhs) && lhs == 0.0 && second.IsNull) result = new DreamValue(1);
else if (first.IsNull && second.TryGetValueAsFloat(out float rhs) && rhs == 0.0) result = new DreamValue(1);
else if (first.IsNull && second.TryGetValueAsString(out var s) && s == "") result = new DreamValue(1);
else result = new DreamValue((IsEqual(first, second) || IsLessThan(first, second)) ? 1 : 0);
Expand Down Expand Up @@ -2963,6 +2983,26 @@ private static bool IsEquivalent(DreamValue first, DreamValue second) {
return IsEqual(first, second);
}

private static bool TryGetReferenceComparisonResult(DreamValue first, DreamValue second,
out DreamValue result) {
// BYOND leaves the left operand untouched whenever the right operand is reference-like regardless of the left operand's runtime representation
// The reverse is a runtime error: reference <op> null
if (!second.IsNull && IsReferenceComparisonType(second.Type)) {
result = first;
return true;
}

result = default;
return false;
}

private static bool IsReferenceComparisonType(DreamValue.DreamValueType type) {
return type is DreamValue.DreamValueType.DreamObject or
DreamValue.DreamValueType.DreamProc or
DreamValue.DreamValueType.DreamResource or
DreamValue.DreamValueType.DreamType;
}
Comment on lines +2999 to +3004

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't like the maintainability issue this creates. BYOND doesn't make this sort of "reference type" distinction, would it be more accurate to say this applies to anything that isn't a number, null, or string?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Probably? I'd need to find time this weekend for further testing.


private static bool IsGreaterThan(DreamValue first, DreamValue second) {
switch (first.Type) {
case DreamValue.DreamValueType.Float when second.Type == DreamValue.DreamValueType.Float:
Expand Down
2 changes: 2 additions & 0 deletions OpenDreamRuntime/Procs/DMProc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ public sealed class DMProcState : ProcState {
{DreamProcOpcode.PickWeighted, DMOpcodeHandlers.PickWeighted},
{DreamProcOpcode.Increment, DMOpcodeHandlers.Increment},
{DreamProcOpcode.Decrement, DMOpcodeHandlers.Decrement},
{DreamProcOpcode.PreIncrement, DMOpcodeHandlers.PreIncrement},
{DreamProcOpcode.PreDecrement, DMOpcodeHandlers.PreDecrement},
{DreamProcOpcode.CompareEquivalent, DMOpcodeHandlers.CompareEquivalent},
{DreamProcOpcode.CompareNotEquivalent, DMOpcodeHandlers.CompareNotEquivalent},
{DreamProcOpcode.Throw, DMOpcodeHandlers.Throw},
Expand Down
2 changes: 2 additions & 0 deletions OpenDreamRuntime/Procs/ProcDecoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ public ITuple DecodeInstruction() {
case DreamProcOpcode.Combine:
case DreamProcOpcode.Increment:
case DreamProcOpcode.Decrement:
case DreamProcOpcode.PreIncrement:
case DreamProcOpcode.PreDecrement:
case DreamProcOpcode.Mask:
case DreamProcOpcode.MultiplyReference:
case DreamProcOpcode.DivideReference:
Expand Down
Loading