diff --git a/Content.Tests/DMProject/Tests/List/alist/AListAdd.dm b/Content.Tests/DMProject/Tests/List/alist/AListAdd.dm new file mode 100644 index 0000000000..deca59ec9c --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListAdd.dm @@ -0,0 +1,9 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + AL.Add("c", "d") + ASSERT(AL["c"] == -4) + ASSERT(AL["d"] == null) + + AL.Add(list("c", "d")) + ASSERT(AL["c"] == -4) + ASSERT(AL["d"] == null) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListBadCopy.dm b/Content.Tests/DMProject/Tests/List/alist/AListBadCopy.dm new file mode 100644 index 0000000000..1a36005af6 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListBadCopy.dm @@ -0,0 +1,6 @@ +// RUNTIME ERROR + +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + var/alist/AL2 + AL2 = AL.Copy(1, 2) diff --git a/Content.Tests/DMProject/Tests/List/alist/AListCompoundedLists.dm b/Content.Tests/DMProject/Tests/List/alist/AListCompoundedLists.dm new file mode 100644 index 0000000000..91eca52596 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListCompoundedLists.dm @@ -0,0 +1,7 @@ +/proc/RunTest() + var/alist/AL = alist("left"=alist("one"=1,"two"=2),"right"=1) + ASSERT(istype(AL["left"], /alist)) + ASSERT(AL["left"]["one"] == 1) + ASSERT(AL["left"]["two"] == 2) + ASSERT(AL["right"] == 1) + ASSERT(!("two" in AL)) diff --git a/Content.Tests/DMProject/Tests/List/alist/AListCopy.dm b/Content.Tests/DMProject/Tests/List/alist/AListCopy.dm new file mode 100644 index 0000000000..b003301b0a --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListCopy.dm @@ -0,0 +1,8 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + var/alist/AL2 + AL2 = AL.Copy() + ASSERT(AL["a"] == 1) + ASSERT(AL2["a"] == 1) + ASSERT(AL2["b"] == 2) + ASSERT("c" in AL2) diff --git a/Content.Tests/DMProject/Tests/List/alist/AListCut.dm b/Content.Tests/DMProject/Tests/List/alist/AListCut.dm new file mode 100644 index 0000000000..af511610b9 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListCut.dm @@ -0,0 +1,5 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + AL.Cut() + ASSERT(length(AL) == 0) + ASSERT(!("a" in AL)) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListFind.dm b/Content.Tests/DMProject/Tests/List/alist/AListFind.dm new file mode 100644 index 0000000000..c654924bf1 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListFind.dm @@ -0,0 +1,5 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + ASSERT(AL.Find("a")) + ASSERT(AL.Find("a", 2, 3)) + ASSERT(!AL.Find("d")) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListInsert.dm b/Content.Tests/DMProject/Tests/List/alist/AListInsert.dm new file mode 100644 index 0000000000..4f2e7a6f02 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListInsert.dm @@ -0,0 +1,5 @@ +// RUNTIME ERROR + +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = -2, "c" = 5.05) + AL.Insert(2, "d", "e") \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListJoin.dm b/Content.Tests/DMProject/Tests/List/alist/AListJoin.dm new file mode 100644 index 0000000000..cf59ccc173 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListJoin.dm @@ -0,0 +1,6 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + var/string_output = AL.Join(",") + ASSERT(string_output == "a,b,c") + string_output = AL.Join("", 2) + ASSERT(string_output == "bc") \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListProcInteractions.dm b/Content.Tests/DMProject/Tests/List/alist/AListProcInteractions.dm new file mode 100644 index 0000000000..559ca992de --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListProcInteractions.dm @@ -0,0 +1,20 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + values_cut_over(AL, 0) + ASSERT(AL["a"] == null) + ASSERT(AL["c"] == -4) + ASSERT(length(AL) == 1) + + AL = alist("a" = 1, "b" = 2, "c" = -4) + values_cut_under(AL, 0) + ASSERT(AL["a"] == 1) + ASSERT(AL["c"] == null) + ASSERT(length(AL) == 2) + + AL = alist("a" = 1, "b" = 2, "c" = -4) + var/alist/AL2 = alist("c" = 5, "d" = 6, "e" = 2) + ASSERT(values_dot(AL, AL2) == -20) + + AL = alist("a" = 1, "b" = 2, "c" = -5) + ASSERT(values_product(AL) == -10) + ASSERT(values_sum(AL) == -2) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListRemove.dm b/Content.Tests/DMProject/Tests/List/alist/AListRemove.dm new file mode 100644 index 0000000000..eeb8b5fe72 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListRemove.dm @@ -0,0 +1,6 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + ASSERT(AL.Remove("a", "b")) + ASSERT(!("a" in AL)) + ASSERT(!("b" in AL)) + ASSERT("c" in AL) diff --git a/Content.Tests/DMProject/Tests/List/alist/AListRemoveAll.dm b/Content.Tests/DMProject/Tests/List/alist/AListRemoveAll.dm new file mode 100644 index 0000000000..3c57093942 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListRemoveAll.dm @@ -0,0 +1,6 @@ +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = 2, "c" = -4) + ASSERT(AL.RemoveAll("a", "b")) + ASSERT(!("a" in AL)) + ASSERT(!("b" in AL)) + ASSERT("c" in AL) diff --git a/Content.Tests/DMProject/Tests/List/alist/AListSetGet.dm b/Content.Tests/DMProject/Tests/List/alist/AListSetGet.dm new file mode 100644 index 0000000000..4cf093a066 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListSetGet.dm @@ -0,0 +1,7 @@ +/proc/RunTest() + var/alist/A1 = alist("1" = 1) + ASSERT(A1["1"] == 1) + A1["1"] = 1.5 + A1["2"] = 2 + ASSERT(A1["1"] == 1.5) + ASSERT(A1["2"] == 2) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListSplice.dm b/Content.Tests/DMProject/Tests/List/alist/AListSplice.dm new file mode 100644 index 0000000000..73902156bb --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListSplice.dm @@ -0,0 +1,5 @@ +// RUNTIME ERROR + +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = -2, "c" = 5.05) + AL.Splice(1, 1, "d") \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListSwap.dm b/Content.Tests/DMProject/Tests/List/alist/AListSwap.dm new file mode 100644 index 0000000000..3268078b44 --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListSwap.dm @@ -0,0 +1,5 @@ +// RUNTIME ERROR + +/proc/RunTest() + var/alist/AL = alist("a" = 1, "b" = -2, "c" = 5.05) + AL.Swap(1, 1) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListValidKeys.dm b/Content.Tests/DMProject/Tests/List/alist/AListValidKeys.dm new file mode 100644 index 0000000000..789dc509be --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListValidKeys.dm @@ -0,0 +1,14 @@ +/proc/RunTest() + var/alist/AL = alist() + var/datum/D = new() + var/list/L = new() + AL[D] = 1 + AL["A"] = 2 + AL[5] = 3 + AL[L] = 4 + AL[null] = 5 + ASSERT(AL[D] == 1) + ASSERT(AL["A"] == 2) + ASSERT(AL[5] == 3) + ASSERT(AL[L] == 4) + ASSERT(AL[null] == 5) \ No newline at end of file diff --git a/Content.Tests/DMProject/Tests/List/alist/AListValidValues.dm b/Content.Tests/DMProject/Tests/List/alist/AListValidValues.dm new file mode 100644 index 0000000000..845757216a --- /dev/null +++ b/Content.Tests/DMProject/Tests/List/alist/AListValidValues.dm @@ -0,0 +1,14 @@ +/proc/RunTest() + var/alist/AL = alist() + var/datum/D = new() + var/list/L = new() + AL[1] = D + AL[2] = L + AL[3] = 3 + AL[4] = "4" + AL[5] = null + ASSERT(AL[1] == D) + ASSERT(AL[2] == L) + ASSERT(AL[3] == 3) + ASSERT(AL[4] == "4") + ASSERT(AL[5] == null) \ No newline at end of file diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index c0b517c479..9555cdf85d 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -148,6 +148,8 @@ public IEnumerable GetAllDescendants(TreeEntry treeEntry) { public DreamObject CreateObject(TreeEntry type) { if (type == List) return CreateList(); + if (type == AssocList) + return CreateAssocList(); if (type == Savefile) return new DreamObjectSavefile(Savefile.ObjectDefinition); if (type.ObjectDefinition.IsSubtypeOf(DatabaseQuery)) diff --git a/OpenDreamRuntime/Objects/Types/DreamAssocList.cs b/OpenDreamRuntime/Objects/Types/DreamAssocList.cs index 3a37f90899..b15bdc8ffa 100644 --- a/OpenDreamRuntime/Objects/Types/DreamAssocList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamAssocList.cs @@ -1,18 +1,26 @@ +using System.Linq; +using OpenDreamRuntime.Procs; + namespace OpenDreamRuntime.Objects.Types; -// TODO: An arglist given to New() can be used to initialize an alist with values public sealed class DreamAssocList(DreamObjectDefinition aListDef, int size) : DreamObject(aListDef), IDreamList { public bool IsAssociative => true; private readonly Dictionary _values = new(size); + public DreamAssocList(DreamObjectDefinition listDef, Dictionary? values) : this(listDef, values?.Count ?? 0) { + if (values != null) { + _values = values; + } + } + public void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { _values[key] = value; } public DreamValue GetValue(DreamValue key) { if (!_values.TryGetValue(key, out var value)) - throw new Exception($"No value with the key {key}"); + return DreamValue.Null; return value; } @@ -21,10 +29,42 @@ public bool ContainsKey(DreamValue key) { return _values.ContainsKey(key); } + public override DreamValue OperatorIndex(DreamValue index, DMProcState state) { + return GetValue(index); + } + public IEnumerable EnumerateValues() { return _values.Keys; // The keys, counter-intuitively } + public int GetLength() { + return _values.Count; + } + + public void Cut(int start = 1, int end = 0) { + if (start != 1) { + throw new Exception($"Cut() was called with non-default start value of {start}."); + } + + if (end != 0) { + throw new Exception($"Cut() was called with non-default end value of {end}."); + } + + _values.Clear(); + } + + public List GetValues() { + return _values.Keys.ToList(); + } + + public Dictionary GetAssociativeValues() { + return _values; + } + + public void RemoveValue(DreamValue value) { + _values.Remove(value); + } + public IEnumerable> EnumerateAssocValues() { return _values; } @@ -39,4 +79,55 @@ public DreamValue[] CopyToArray() { public Dictionary CopyAssocValues() { return new(_values); } + + public void AddValue(DreamValue value) { + if(ContainsValue(value)) { + return; // calling Add("c") on alist("c" = 5) does not change anything + } + + _values[value] = DreamValue.Null; + } + + public IDreamList CreateCopy(int start = 1, int end = 0) { + if (start == 0) ++start; //start being 0 and start being 1 are equivalent + + var values = GetValues(); + if (end > values.Count + 1 || start > values.Count + 1) throw new Exception("list index out of bounds"); + if (end == 0) end = values.Count + 1; + if (end <= start) + return new DreamAssocList(ObjectDefinition, 0); + + Dictionary copyValues = new(_values); + + return new DreamAssocList(ObjectDefinition, copyValues); + } + + public int FindValue(DreamValue value, int start = 1, int end = 0) { + // Unlike list.Find(), alist.Find() doesn't pay attention to start and end, and returns a boolean 0/1 instead of the position of the found object + if(ContainsValue(value)) { + return 1; + } + + return 0; + } + + public void Insert(int index, DreamValue value) { + throw new Exception("insert not allowed for this list"); + } + + public void Swap(int index1, int index2) { + throw new Exception("swap not allowed for this list"); + } + + public bool ContainsValue(DreamValue value) { + var keys = GetValues(); + foreach (var key in keys) { + if (key.Equals(value)) { + return true; + } + } + + return false; + } + } diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index e85b562598..ed8426b190 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -104,14 +104,14 @@ protected override void HandleDeletion(bool possiblyThreaded) { base.HandleDeletion(possiblyThreaded); } - public DreamList CreateCopy(int start = 1, int end = 0) { + public IDreamList CreateCopy(int start = 1, int end = 0) { if (start == 0) ++start; //start being 0 and start being 1 are equivalent var values = GetValues(); if (end > values.Count + 1 || start > values.Count + 1) throw new Exception("list index out of bounds"); if (end == 0) end = values.Count + 1; if (end <= start) - return new(ObjectDefinition, 0); + return new DreamList(ObjectDefinition, 0); List copyValues = values.GetRange(start - 1, end - start); @@ -124,7 +124,7 @@ public DreamList CreateCopy(int start = 1, int end = 0) { } } - return new(ObjectDefinition, copyValues, associativeValues); + return new DreamList(ObjectDefinition, copyValues, associativeValues); } /// @@ -333,7 +333,7 @@ public override void OperatorIndexAssign(DreamValue index, DMProcState state, Dr } public override DreamValue OperatorAdd(DreamValue b, DMProcState state) { - DreamList listCopy = CreateCopy(); + DreamList listCopy = (DreamList)CreateCopy(); if (b.TryGetValueAsDreamList(out var bList)) { foreach (DreamValue value in bList.EnumerateValues()) { @@ -351,7 +351,7 @@ public override DreamValue OperatorAdd(DreamValue b, DMProcState state) { } public override DreamValue OperatorSubtract(DreamValue b, DMProcState state) { - DreamList listCopy = CreateCopy(); + DreamList listCopy = (DreamList)CreateCopy(); if (b.TryGetValueAsDreamList(out var bList)) { foreach (DreamValue value in bList.EnumerateValues()) { @@ -370,7 +370,7 @@ public override DreamValue OperatorOr(DreamValue b, DMProcState state) { if (b.TryGetValueAsDreamList(out var bList)) { // List | List list = Union(bList); } else { // List | x - list = CreateCopy(); + list = (DreamList)CreateCopy(); list.AddValue(b); } diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs b/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs index fa88353320..aa08641fdf 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectImage.cs @@ -132,7 +132,7 @@ protected override void SetVar(string varName, DreamValue value) { // Otherwise it attempts to create an appearance and creates a new (normal) list with that appearance if (ObjectDefinition.IsSubtypeOf(ObjectTree.MutableAppearance)) { if (valueList != null) { - _overlays = valueList.CreateCopy(); + _overlays = (DreamList)valueList.CreateCopy(); } else { var overlay = DreamOverlaysList.CreateOverlayAppearance(AtomManager, value, AtomManager.MustGetAppearance(this).Icon); if (overlay == null) @@ -165,7 +165,7 @@ protected override void SetVar(string varName, DreamValue value) { // See the comment in the overlays setter for info on this if (ObjectDefinition.IsSubtypeOf(ObjectTree.MutableAppearance)) { if (valueList != null) { - _underlays = valueList.CreateCopy(); + _underlays = (DreamList)valueList.CreateCopy(); } else { var underlay = DreamOverlaysList.CreateOverlayAppearance(AtomManager, value, AtomManager.MustGetAppearance(this).Icon); if (underlay == null) diff --git a/OpenDreamRuntime/Objects/Types/IDreamList.cs b/OpenDreamRuntime/Objects/Types/IDreamList.cs index 64b9593280..c8f53b3e8c 100644 --- a/OpenDreamRuntime/Objects/Types/IDreamList.cs +++ b/OpenDreamRuntime/Objects/Types/IDreamList.cs @@ -3,19 +3,30 @@ namespace OpenDreamRuntime.Objects.Types; public interface IDreamList { - public bool IsAssociative { get; } + bool IsAssociative { get; } - public void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false); - public DreamValue GetValue(DreamValue key); - public bool ContainsKey(DreamValue key); - public IEnumerable EnumerateValues(); - public IEnumerable> EnumerateAssocValues(); + void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false); + DreamValue GetValue(DreamValue key); + bool ContainsKey(DreamValue key); + IEnumerable EnumerateValues(); + int GetLength(); + void Cut(int start = 1, int end = 0); + List GetValues(); + Dictionary GetAssociativeValues(); + void RemoveValue(DreamValue value); + IEnumerable> EnumerateAssocValues(); + void AddValue(DreamValue value); + IDreamList CreateCopy(int start = 1, int end = 0); + int FindValue(DreamValue value, int start = 1, int end = 0); + void Insert(int index, DreamValue value); + bool ContainsValue(DreamValue value); + void Swap(int index1, int index2); - public DreamValue[] CopyToArray() { + DreamValue[] CopyToArray() { return EnumerateValues().ToArray(); } - public Dictionary CopyAssocValues() { + Dictionary CopyAssocValues() { return new(EnumerateAssocValues()); } } diff --git a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs index 85e652d479..51f8143b0f 100644 --- a/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs +++ b/OpenDreamRuntime/Procs/DMOpcodeHandlers.cs @@ -679,7 +679,7 @@ public static ProcStatus IsInList(DMProcState state) { DreamValue value = state.Pop(); if (listValue.TryGetValueAsDreamObject(out var listObject) && listObject != null) { - DreamList? list = listObject as DreamList; + IDreamList? list = listObject as IDreamList; if (list == null) { if (listObject is DreamObjectAtom or DreamObjectWorld) { diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index a8b2a1131a..a536defe55 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -138,6 +138,17 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Splice); objectTree.SetNativeProc(objectTree.List, DreamProcNativeList.NativeProc_Swap); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Add); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Copy); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Cut); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Find); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Insert); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Join); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Remove); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_RemoveAll); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Splice); + objectTree.SetNativeProc(objectTree.AssocList, DreamProcNativeList.NativeProc_Swap); + objectTree.SetNativeProc(objectTree.Matrix, DreamProcNativeMatrix.NativeProc_Add); objectTree.SetNativeProc(objectTree.Matrix, DreamProcNativeMatrix.NativeProc_Invert); objectTree.SetNativeProc(objectTree.Matrix, DreamProcNativeMatrix.NativeProc_Multiply); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs index 88b642855a..5e4d9b1ce9 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeList.cs @@ -8,7 +8,7 @@ internal static class DreamProcNativeList { [DreamProc("Add")] [DreamProcParameter("Item1")] public static DreamValue NativeProc_Add(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; foreach (var argument in bundle.Arguments) { if (argument.TryGetValueAsDreamList(out var argumentList)) { @@ -29,10 +29,20 @@ public static DreamValue NativeProc_Add(NativeProc.Bundle bundle, DreamObject? s public static DreamValue NativeProc_Copy(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { int start = bundle.GetArgument(0, "Start").MustGetValueAsInteger(); //1-indexed int end = bundle.GetArgument(1, "End").MustGetValueAsInteger(); //1-indexed - DreamList list = (DreamList)src!; - DreamList listCopy = list.CreateCopy(start, end); + IDreamList list = (IDreamList)src!; + if (list is DreamList) { + DreamList listCopy = (DreamList)list.CreateCopy(start, end); + return new DreamValue(listCopy); + } else if (list is DreamAssocList) { + if (start != 1 || end != 0) { + throw new Exception("list index out of bounds"); + } - return new DreamValue(listCopy); + DreamAssocList listCopy = (DreamAssocList)list.CreateCopy(start, end); + return new DreamValue(listCopy); + } else { + throw new Exception("Unhandled IDreamList child in NativeProc_Copy."); + } } [DreamProc("Cut")] @@ -41,7 +51,7 @@ public static DreamValue NativeProc_Copy(NativeProc.Bundle bundle, DreamObject? public static DreamValue NativeProc_Cut(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { int start = bundle.GetArgument(0, "Start").MustGetValueAsInteger(); //1-indexed int end = bundle.GetArgument(1, "End").MustGetValueAsInteger(); //1-indexed - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; list.Cut(start, end); return DreamValue.Null; @@ -56,7 +66,7 @@ public static DreamValue NativeProc_Find(NativeProc.Bundle bundle, DreamObject? if (!bundle.GetArgument(1, "Start").TryGetValueAsInteger(out var start)) //1-indexed start = 1; // 1 if non-number bundle.GetArgument(2, "End").TryGetValueAsInteger(out var end); //1-indexed, 0 if non-number - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; return new(list.FindValue(element, start, end)); } @@ -91,7 +101,7 @@ public static DreamValue NativeProc_Insert(NativeProc.Bundle bundle, DreamObject [DreamProcParameter("Start", Type = DreamValueTypeFlag.Float, DefaultValue = 1)] [DreamProcParameter("End", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] public static DreamValue NativeProc_Join(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; List values = list.GetValues(); bundle.GetArgument(0, "Glue").TryGetValueAsString(out var glue); @@ -122,14 +132,14 @@ public static DreamValue NativeProc_Join(NativeProc.Bundle bundle, DreamObject? [DreamProc("Remove")] [DreamProcParameter("Item1")] public static DreamValue NativeProc_Remove(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; return new DreamValue(ListRemove(list, bundle.Arguments) > 0 ? 1 : 0); } [DreamProc("RemoveAll")] [DreamProcParameter("Item1")] public static DreamValue NativeProc_RemoveAll(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; var totalRemoved = 0; int removed; do { @@ -140,7 +150,7 @@ public static DreamValue NativeProc_RemoveAll(NativeProc.Bundle bundle, DreamObj return new DreamValue(totalRemoved); } - private static int ListRemove(DreamList list, ReadOnlySpan args) { + private static int ListRemove(IDreamList list, ReadOnlySpan args) { var itemRemoved = 0; foreach (var argument in args) { if (argument.TryGetValueAsDreamList(out var argumentList)) { @@ -170,7 +180,10 @@ private static int ListRemove(DreamList list, ReadOnlySpan args) { public static DreamValue NativeProc_Splice(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { int startIndex = bundle.GetArgument(0, "Start").MustGetValueAsInteger(); //1-indexed int end = bundle.GetArgument(1, "End").MustGetValueAsInteger(); //1-indexed - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; + if (list is DreamAssocList) { + throw new Exception("special list may not be spliced"); + } list.Cut(startIndex, end); @@ -197,7 +210,7 @@ public static DreamValue NativeProc_Splice(NativeProc.Bundle bundle, DreamObject [DreamProcParameter("Index1", Type = DreamValueTypeFlag.Float)] [DreamProcParameter("Index2", Type = DreamValueTypeFlag.Float)] public static DreamValue NativeProc_Swap(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { - DreamList list = (DreamList)src!; + IDreamList list = (IDreamList)src!; int index1 = bundle.GetArgument(0, "Index1").MustGetValueAsInteger(); int index2 = bundle.GetArgument(1, "Index2").MustGetValueAsInteger(); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 25f6d67819..6928ded402 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -1594,7 +1594,7 @@ public static DreamValue NativeProc_json_encode(NativeProc.Bundle bundle, DreamO public static DreamValue _length(DreamValue value, bool countBytes) { if (value.TryGetValueAsString(out var str)) { return new DreamValue(countBytes ? str.Length : str.EnumerateRunes().Count()); - } else if (value.TryGetValueAsDreamList(out var list)) { + } else if (value.TryGetValueAsIDreamList(out var list)) { return new DreamValue(list.GetLength()); } else if (value.Type is DreamValueType.Float or DreamValueType.DreamObject or DreamValueType.DreamType) { return new DreamValue(0); @@ -3256,10 +3256,10 @@ public static DreamValue NativeProc_values_cut_over(NativeProc.Bundle bundle, Dr if (bundle.Arguments.Length < 2 || bundle.Arguments.Length > 3) throw new Exception($"expected 2-3 arguments (found {bundle.Arguments.Length})"); DreamValue argList = bundle.GetArgument(0, "Alist"); - DreamValue argMin = bundle.GetArgument(1, "Max"); + DreamValue argMax = bundle.GetArgument(1, "Max"); DreamValue argInclusive = bundle.GetArgument(2, "inclusive"); - return values_cut_helper(argList, argMin, argInclusive, false); + return values_cut_helper(argList, argMax, argInclusive, false); } [DreamProc("values_cut_under")] @@ -3283,7 +3283,7 @@ private static DreamValue values_cut_helper(DreamValue argList, DreamValue argMi var cutCount = 0; // number of values cut from the list var min = argMin.UnsafeGetValueAsFloat(); - if (argList.TryGetValueAsDreamList(out var list)) { + if (argList.TryGetValueAsIDreamList(out var list)) { if (!list.IsAssociative) { cutCount = list.GetLength(); list.Cut(); @@ -3337,7 +3337,7 @@ public static DreamValue NativeProc_values_dot(NativeProc.Bundle bundle, DreamOb float sum = 0; // Default return is 0 for invalid args - if (argA.TryGetValueAsDreamList(out var listA) && listA.IsAssociative && argB.TryGetValueAsDreamList(out var listB) && listB.IsAssociative) { + if (argA.TryGetValueAsIDreamList(out var listA) && listA.IsAssociative && argB.TryGetValueAsIDreamList(out var listB) && listB.IsAssociative) { var aValues = listA.GetAssociativeValues(); var bValues = listB.GetAssociativeValues(); @@ -3364,7 +3364,7 @@ public static DreamValue NativeProc_values_product(NativeProc.Bundle bundle, Dre float product = 1; // Default return is 1 for invalid args - if (arg.TryGetValueAsDreamList(out var list) && list.IsAssociative) { + if (arg.TryGetValueAsIDreamList(out var list) && list.IsAssociative) { var assocValues = list.GetAssociativeValues(); foreach (var (_,value) in assocValues) { if(value.TryGetValueAsFloat(out var valFloat)) product *= valFloat; @@ -3383,7 +3383,7 @@ public static DreamValue NativeProc_values_sum(NativeProc.Bundle bundle, DreamOb float sum = 0; // Default return is 0 for invalid args - if (arg.TryGetValueAsDreamList(out var list) && list.IsAssociative) { + if (arg.TryGetValueAsIDreamList(out var list) && list.IsAssociative) { var assocValues = list.GetAssociativeValues(); foreach (var (_,value) in assocValues) { if(value.TryGetValueAsFloat(out var valFloat)) sum += valFloat;