From 61be0d9f89c270ea547d6e9dbb349a18f5e3ec3c Mon Sep 17 00:00:00 2001 From: Romayne <27376947+MeggalBozale@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:24:18 -0400 Subject: [PATCH 1/5] piggyback off existing procs instead of doing anything fancy --- DMCompiler/DMStandard/_Standard.dm | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 3df53f9234..4108175acc 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -135,8 +135,7 @@ proc/winset(player, control_id, params) #include "UnsortedAdditions.dm" proc/replacetextEx_char(Haystack as text, Needle, Replacement, Start = 1, End = 0) as text - set opendream_unimplemented = TRUE - return Haystack + return jointext(splittext(Haystack, Needle, Start, End), Replacement) /proc/step(atom/movable/Ref as /atom/movable, var/Dir, var/Speed=0) as num //TODO: Speed = step_size if Speed is 0 From 4bda844ca02149d0cfe154fa5f2df33a83d2655d Mon Sep 17 00:00:00 2001 From: Romayne <27376947+MeggalBozale@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:09:33 -0400 Subject: [PATCH 2/5] fix splittext, update splittext method tests to reflect new DM behavior --- .../DMProject/Tests/Text/Splittext.dm | 12 +++++------ .../Procs/Native/DreamProcNativeRoot.cs | 21 ++++++++++++++----- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Content.Tests/DMProject/Tests/Text/Splittext.dm b/Content.Tests/DMProject/Tests/Text/Splittext.dm index da2d898418..be415a4346 100644 --- a/Content.Tests/DMProject/Tests/Text/Splittext.dm +++ b/Content.Tests/DMProject/Tests/Text/Splittext.dm @@ -5,19 +5,19 @@ ASSERT(test1 ~= test1_expected) var/list/test2 = splittext(test_text, " ", 5) - var/test2_expected = list("average","of","1,","2,","3,","4,","5","is:","3") + var/test2_expected = list("The average","of","1,","2,","3,","4,","5","is:","3") ASSERT(test2 ~= test2_expected) var/list/test3 = splittext(test_text, " ", 5, 10) - var/test3_expected = list("avera") + var/test3_expected = list("The average of 1, 2, 3, 4, 5 is: 3") ASSERT(test3 ~= test3_expected) var/list/test4 = splittext(test_text, " ", 10, 20) - var/test4_expected = list("ge","of","1,","2") + var/test4_expected = list("The average","of","1,","2, 3, 4, 5 is: 3") ASSERT(test4 ~= test4_expected) var/list/test5 = splittext(test_text, " ", 10, 20, 1) - var/test5_expected = list("ge"," ","of"," ","1,"," ","2") + var/test5_expected = list("The average"," ","of"," ","1,"," ","2, 3, 4, 5 is: 3") ASSERT(test5 ~= test5_expected) //it's regex time @@ -26,9 +26,9 @@ ASSERT(test6 ~= test6_expected) var/test7 = splittext(test_text, regex(@"\d"), 5, 30) - var/test7_expected = list("average of ",", ",", ",", ",", "," ") + var/test7_expected = list("The average of ",", ",", ",", ",", "," is: 3") ASSERT(test7 ~= test7_expected) var/test8 = splittext(test_text, regex(@"\d"), 5, 30, 1) - var/test8_expected = list("average of ","1",", ","2",", ","3",", ","4",", ","5"," ") + var/test8_expected = list("The average of ","1",", ","2",", ","3",", ","4",", ","5"," is: 3") ASSERT(test8 ~= test8_expected) \ No newline at end of file diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index ebf453647b..7e23c1d9f6 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -2615,6 +2615,10 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj if(bundle.GetArgument(4, "include_delimiters").TryGetValueAsInteger(out var includeDelimitersInt)) includeDelimiters = includeDelimitersInt != 0; //idk why BYOND doesn't just use truthiness, but it doesn't, so... + // save the start/end beforehand to staple back on at the end + string startText = text[0..Math.Max(start,0)]; + string endText = text[Math.Min(end, text.Length)..]; + if(start > 0 || end < text.Length) text = text[Math.Max(start,0)..Math.Min(end, text.Length)]; @@ -2622,7 +2626,7 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj if (delim.TryGetValueAsDreamObject(out var regexObject)) { if(includeDelimiters) { - var values = new List(); + var values = new List {startText}; int pos = 0; foreach (Match m in regexObject.Regex.Matches(text)) { values.Add(text.Substring(pos, m.Index - pos)); @@ -2630,15 +2634,19 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj pos = m.Index + m.Length; } values.Add(text.Substring(pos)); + values.Add(endText); return new DreamValue(bundle.ObjectTree.CreateList(values.ToArray())); } else { - return new DreamValue(bundle.ObjectTree.CreateList(regexObject.Regex.Split(text))); + List values = new List {startText}; + values.AddRange(regexObject.Regex.Split(text)); + values.Add(endText); + return new DreamValue(bundle.ObjectTree.CreateList(values.ToArray())); } } else if (delim.TryGetValueAsString(out var delimiter)) { string[] splitText; if(includeDelimiters) { - //basically split on delimeter, and then add the delimiter back in after each split (except the last one) - splitText= text.Split(delimiter); + //basically split on delimiter, and then add the delimiter back in after each split (except the last one) + splitText = text.Split(delimiter); string[] longerSplitText = new string[splitText.Length * 2 - 1]; for(int i = 0; i < splitText.Length; i++) { longerSplitText[i * 2] = splitText[i]; @@ -2649,7 +2657,10 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj } else { splitText = text.Split(delimiter); } - return new DreamValue(bundle.ObjectTree.CreateList(splitText)); + List finalList = new List {startText}; + finalList.AddRange(splitText); + finalList.Add(endText); + return new DreamValue(bundle.ObjectTree.CreateList(finalList.ToArray())); } else { return new DreamValue(bundle.ObjectTree.CreateList()); } From cf829c8efaa1cc6016cc4771fd424839b888de0d Mon Sep 17 00:00:00 2001 From: Romayne <27376947+MeggalBozale@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:18:19 -0400 Subject: [PATCH 3/5] fix Splittext tests for 515, update Splittext implementation so it passes tests --- .../Procs/Native/DreamProcNativeRoot.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 7e23c1d9f6..22082a6cc9 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -2618,6 +2618,7 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj // save the start/end beforehand to staple back on at the end string startText = text[0..Math.Max(start,0)]; string endText = text[Math.Min(end, text.Length)..]; + // figure out if we should be adding the start/end in the first place if(start > 0 || end < text.Length) text = text[Math.Max(start,0)..Math.Min(end, text.Length)]; @@ -2626,7 +2627,7 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj if (delim.TryGetValueAsDreamObject(out var regexObject)) { if(includeDelimiters) { - var values = new List {startText}; + var values = new List(); int pos = 0; foreach (Match m in regexObject.Regex.Matches(text)) { values.Add(text.Substring(pos, m.Index - pos)); @@ -2634,13 +2635,15 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj pos = m.Index + m.Length; } values.Add(text.Substring(pos)); - values.Add(endText); - return new DreamValue(bundle.ObjectTree.CreateList(values.ToArray())); + string[] arrayValues = values.ToArray(); + arrayValues[0] = startText + arrayValues[0]; + arrayValues[arrayValues.Length-1] = arrayValues[arrayValues.Length-1] + endText; + return new DreamValue(bundle.ObjectTree.CreateList(arrayValues)); } else { - List values = new List {startText}; - values.AddRange(regexObject.Regex.Split(text)); - values.Add(endText); - return new DreamValue(bundle.ObjectTree.CreateList(values.ToArray())); + string[] values = regexObject.Regex.Split(text); + values[0] = startText + values[0]; + values[values.Length-1] = values[values.Length-1] + endText; + return new DreamValue(bundle.ObjectTree.CreateList(values)); } } else if (delim.TryGetValueAsString(out var delimiter)) { string[] splitText; @@ -2657,10 +2660,9 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj } else { splitText = text.Split(delimiter); } - List finalList = new List {startText}; - finalList.AddRange(splitText); - finalList.Add(endText); - return new DreamValue(bundle.ObjectTree.CreateList(finalList.ToArray())); + splitText[0] = startText + splitText[0]; + splitText[splitText.Length-1] = splitText[splitText.Length-1] + endText; + return new DreamValue(bundle.ObjectTree.CreateList(splitText)); } else { return new DreamValue(bundle.ObjectTree.CreateList()); } From c8f8ca07fda5a76cf4c8f08d0f24901582fe4f81 Mon Sep 17 00:00:00 2001 From: Romayne <27376947+MeggalBozale@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:42:41 -0400 Subject: [PATCH 4/5] i actually dont know whats happening anymore --- .../DMProject/Tests/Text/Splittext.dm | 116 ++++++++++++++---- .../Procs/Native/DreamProcNativeRoot.cs | 112 ++++++++++++++--- 2 files changed, 181 insertions(+), 47 deletions(-) diff --git a/Content.Tests/DMProject/Tests/Text/Splittext.dm b/Content.Tests/DMProject/Tests/Text/Splittext.dm index be415a4346..15dbad809d 100644 --- a/Content.Tests/DMProject/Tests/Text/Splittext.dm +++ b/Content.Tests/DMProject/Tests/Text/Splittext.dm @@ -1,34 +1,96 @@ +/proc/GetFailMessage(var/test_pair) + var/O_str_got = "" + var/O_str_expected = "" + for (var/i in test_pair[1]) + O_str_got += "\"[i]\", " + for (var/i in test_pair[2]) + O_str_expected += "\"[i]\", " + return "Got:\n[O_str_got]\nexpected:\n[O_str_expected]\n" + /proc/RunTest() var/test_text = "The average of 1, 2, 3, 4, 5 is: 3" - var/list/test1 = splittext(test_text, " ") - var/list/test1_expected = list("The","average","of","1,","2,","3,","4,","5","is:","3") - ASSERT(test1 ~= test1_expected) - - var/list/test2 = splittext(test_text, " ", 5) - var/test2_expected = list("The average","of","1,","2,","3,","4,","5","is:","3") - ASSERT(test2 ~= test2_expected) - - var/list/test3 = splittext(test_text, " ", 5, 10) - var/test3_expected = list("The average of 1, 2, 3, 4, 5 is: 3") - ASSERT(test3 ~= test3_expected) + // A list of pairs of lists which is just the compiled Splittext implementation vs. the DM implementation output. + // To add your own test just create a new element in here of list(splittext(your_args), list(output_dm_gave_for_that_call)) + // Note that as of writing, regex with capturing groups behaves **very** weirdly in DM, and splittext() does not yet replicate that behavior. Other regexes should be ok. + var/list/tests = list( + list(splittext(test_text, regex(@"\d"), 5, 30, 1), list("The average of ","1",", ","2",", ","3",", ","4",", ","5"," is: 3")), + list(splittext(test_text, regex(@"\d"), 5, 30), list("The average of ",", ",", ",", ",", "," is: 3")), + list(splittext(test_text, regex("")), list("T","h","e"," ","a","v","e","r","a","g","e"," ","o","f"," ","1",","," ","2",","," ","3",","," ","4",","," ","5"," ","i","s",":"," ","3","")), + list(splittext(test_text, "", 0, 10, 1), list()), + list(splittext(test_text, regex(""), 0, 0, 1), list("The average of 1, 2, 3, 4, 5 is: 3")), + list(splittext(test_text, ""), list("T","h","e"," ","a","v","e","r","a","g","e"," ","o","f"," ","1",","," ","2",","," ","3",","," ","4",","," ","5"," ","i","s",":"," ","3")), + list(splittext(test_text, "", 5, 10, 1), list("The a","","v","","e","","r","","a","","ge of 1, 2, 3, 4, 5 is: 3")), + list(splittext(test_text, "", 1, length(test_text)+1, 1), list("T","","h","","e",""," ","","a","","v","","e","","r","","a","","g","","e",""," ","","o","","f",""," ","","1","",",",""," ","","2","",",",""," ","","3","",",",""," ","","4","",",",""," ","","5",""," ","","i","","s","",":",""," ","","3")), + list(splittext(test_text, regex(""), 5, 10, 1), list("The a","","v","","e","","r","","age of 1, 2, 3, 4, 5 is: 3")), + // list(splittext(test_text, regex("()"), 1, length(test_text)+1, 1), list("T","","","h","","","e","",""," ","","","a","","","v","","","e","","","r","","","a","","","g","","","e","",""," ","","","o","","","f","",""," ","","","1","","",",","",""," ","","","2","","",",","",""," ","","","3","","",",","",""," ","","","4","","",",","",""," ","","","5","",""," ","","","i","","","s","","",":","",""," ","","","3")), + list(splittext(test_text, regex(@"")), list("T","h","e"," ","a","v","e","r","a","g","e"," ","o","f"," ","1",","," ","2",","," ","3",","," ","4",","," ","5"," ","i","s",":"," ","3","")), + list(splittext(test_text, " "), list("The","average","of","1,","2,","3,","4,","5","is:","3")), + list(splittext(test_text, " ", 5), list("The average","of","1,","2,","3,","4,","5","is:","3")), + list(splittext(test_text, " ", 5, 10), list("The average of 1, 2, 3, 4, 5 is: 3")), + list(splittext(test_text, " ", 10, 20), list("The average","of","1,","2, 3, 4, 5 is: 3")), + list(splittext(test_text, " ", 10, 20, 1), list("The average"," ","of"," ","1,"," ","2, 3, 4, 5 is: 3")), + list(splittext(test_text, regex(@"\d")), list("The average of ",", ",", ",", ",", "," is: ","")), + // list(splittext(test_text, regex(@"(\d)"), 1, length(test_text)+1, 1), list("The average of ","1","1",", ","2","2",", ","3","3",", ","4","4",", ","5","5"," is: ","3","3","")) + ) + // Go through all our tests and ensure that the test results match the expected output + var/t_num = 0 + var/tests_failed = 0 + var/fail_output = "\n" + for (var/test_pair in tests) + t_num += 1 + // Special debug output for failed tests + if (!(test_pair[1] ~= test_pair[2])) + tests_failed = 1 + fail_output += "Failed test [t_num]:\n[GetFailMessage(test_pair)]" - var/list/test4 = splittext(test_text, " ", 10, 20) - var/test4_expected = list("The average","of","1,","2, 3, 4, 5 is: 3") - ASSERT(test4 ~= test4_expected) + if (tests_failed) + CRASH(fail_output) - var/list/test5 = splittext(test_text, " ", 10, 20, 1) - var/test5_expected = list("The average"," ","of"," ","1,"," ","2, 3, 4, 5 is: 3") - ASSERT(test5 ~= test5_expected) + // list(splittext(test_text, regex("")), "splittext(test_text, regex(\"\"))"), + // list(splittext(test_text, "", 0, 10, 1), "splittext(test_text, \"\", 0, 10, 1)"), + // list(splittext(test_text, regex(""), 0, 0, 1), "splittext(test_text, regex(\"\"), 0, 0, 1)"), + // list(splittext(test_text, ""), "splittext(test_text, \"\")"), + // list(splittext(test_text, "", 5, 10, 1), "splittext(test_text, \"\", 5, 10, 1)"), + // list(splittext(test_text, "", 1, length(test_text)+1, 1), "splittext(test_text, \"\", 1, length(test_text)+1, 1)"), + // list(splittext(test_text, regex(""), 5, 10, 1), "splittext(test_text, regex(\"\"), 5, 10, 1)"), + // list(splittext(test_text, regex("()"), 1, length(test_text)+1, 1), "splittext(test_text, regex(\"()\"), 1, length(test_text)+1, 1)"), + // list(splittext(test_text, regex(@"")), "splittext(test_text, regex(@\"\"))"), + // list(splittext(test_text, " "), "splittext(test_text, \" \")"), + // list(splittext(test_text, " ", 5), "splittext(test_text, \" \", 5)"), + // list(splittext(test_text, " ", 5, 10), "splittext(test_text, \" \", 5, 10)"), + // list(splittext(test_text, " ", 10, 20), "splittext(test_text, \" \", 10, 20)"), + // list(splittext(test_text, " ", 10, 20, 1), "splittext(test_text, \" \", 10, 20, 1)"), + // list(splittext(test_text, regex(@"\d")), "splittext(test_text, regex(@\"\\d\"))"), + // list(splittext(test_text, regex(@"\d"), 5, 30), "splittext(test_text, regex(@\"\\d\"), 5, 30)"), + // list(splittext(test_text, regex(@"\d"), 5, 30, 1), "splittext(test_text, regex(@\"\\d\"), 5, 30, 1)"), - //it's regex time - var/test6 = splittext(test_text, regex(@"\d")) - var/test6_expected = list("The average of ",", ",", ",", ",", "," is: ","") - ASSERT(test6 ~= test6_expected) + // var/list/tests = list( + // list(splittext(test_text, regex("")), list("T","h","e"," ","a","v","e","r","a","g","e"," ","o","f"," ","1",","," ","2",","," ","3",","," ","4",","," ","5"," ","i","s",":"," ","3","")), + // list(splittext(test_text, "", 0, 10, 1), list()), + // list(splittext(test_text, regex(""), 0, 0, 1), list("The average of 1, 2, 3, 4, 5 is: 3")), + // list(splittext(test_text, ""), list("T","h","e"," ","a","v","e","r","a","g","e"," ","o","f"," ","1",","," ","2",","," ","3",","," ","4",","," ","5"," ","i","s",":"," ","3")), + // list(splittext(test_text, "", 5, 10, 1), list("The a","","v","","e","","r","","a","","ge of 1, 2, 3, 4, 5 is: 3")), + // list(splittext(test_text, "", 1, length(test_text)+1, 1), list("T","","h","","e",""," ","","a","","v","","e","","r","","a","","g","","e",""," ","","o","","f",""," ","","1","",",",""," ","","2","",",",""," ","","3","",",",""," ","","4","",",",""," ","","5",""," ","","i","","s","",":",""," ","","3")), + // list(splittext(test_text, regex(""), 5, 10, 1), list("The a","","v","","e","","r","","age of 1, 2, 3, 4, 5 is: 3")), + // list(splittext(test_text, regex("()"), 1, length(test_text)+1, 1), list("T","","","h","","","e","",""," ","","","a","","","v","","","e","","","r","","","a","","","g","","","e","",""," ","","","o","","","f","",""," ","","","1","","",",","",""," ","","","2","","",",","",""," ","","","3","","",",","",""," ","","","4","","",",","",""," ","","","5","",""," ","","","i","","","s","","",":","",""," ","","","3")), + // list(splittext(test_text, regex(@"")), list("T","h","e"," ","a","v","e","r","a","g","e"," ","o","f"," ","1",","," ","2",","," ","3",","," ","4",","," ","5"," ","i","s",":"," ","3","")), + // list(splittext(test_text, " "), list("The","average","of","1,","2,","3,","4,","5","is:","3")), + // list(splittext(test_text, " ", 5), list("The average","of","1,","2,","3,","4,","5","is:","3")), + // list(splittext(test_text, " ", 5, 10), list("The average of 1, 2, 3, 4, 5 is: 3")), + // list(splittext(test_text, " ", 10, 20), list("The average","of","1,","2, 3, 4, 5 is: 3")), + // list(splittext(test_text, " ", 10, 20, 1), list("The average"," ","of"," ","1,"," ","2, 3, 4, 5 is: 3")), + // list(splittext(test_text, regex(@"\d")), list("The average of ",", ",", ",", ",", "," is: ","")), + // list(splittext(test_text, regex(@"\d"), 5, 30), list("The average of ",", ",", ",", ",", "," is: 3")), + // list(splittext(test_text, regex(@"\d"), 5, 30, 1), list("The average of ","1",", ","2",", ","3",", ","4",", ","5"," is: 3")), + // ) - var/test7 = splittext(test_text, regex(@"\d"), 5, 30) - var/test7_expected = list("The average of ",", ",", ",", ",", "," is: 3") - ASSERT(test7 ~= test7_expected) - var/test8 = splittext(test_text, regex(@"\d"), 5, 30, 1) - var/test8_expected = list("The average of ","1",", ","2",", ","3",", ","4",", ","5"," is: 3") - ASSERT(test8 ~= test8_expected) \ No newline at end of file +// var/test_text = "The average of 1, 2, 3, 4, 5 is: 3" +// var/list/tests = list( +// list(splittext(test_text, regex(@"\d")), "splittext(test_text, regex(@\"\\d\"))"), +// list(splittext(test_text, regex(@"\d"), 5, 30), "splittext(test_text, regex(@\"\\d\"), 5, 30)"), +// list(splittext(test_text, regex(@"\d"), 5, 30, 1), "splittext(test_text, regex(@\"\\d\"), 5, 30, 1)"), +// ) +// for (var/test in tests) +// var/delimiter = "\",\"" +// world.log << "list([test[2]], list(\"[jointext(test[1], delimiter)]\"))," \ No newline at end of file diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 22082a6cc9..5237604f08 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -2598,19 +2598,50 @@ public static DreamValue NativeProc_splicetext_char(NativeProc.Bundle bundle, Dr [DreamProcParameter("End", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] [DreamProcParameter("include_delimiters", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + // Handle args first if (!bundle.GetArgument(0, "Text").TryGetValueAsString(out var text)) { return new DreamValue(bundle.ObjectTree.CreateList()); } + var delim = bundle.GetArgument(1, "Delimiter"); //can either be a regex or string + + // Handle checking for empty string delimiters for non-regex, and setting up delimiterString/regexPattern + bool isEmptyString = false; + bool isRegex = false; // This exists specifically so that we can mimic DM and add some "" at the end if its not cut off beforehand (god, why) + delim.TryGetValueAsString(out var delimiterString); + if (delim.TryGetValueAsDreamObject(out var regexObject)) { + isRegex = true; + string regexPattern = regexObject.Regex.ToString(); + isEmptyString = string.IsNullOrEmpty(regexPattern); + } + else if (delimiterString is string) { + isEmptyString = string.IsNullOrEmpty(delimiterString); + } + else { + return new DreamValue(bundle.ObjectTree.CreateList()); // No Delimiter arg = Empty list + } + int start = 0; int end = 0; - if(bundle.GetArgument(2, "Start").TryGetValueAsInteger(out start)) + bool gotStart = bundle.GetArgument(2, "Start").TryGetValueAsInteger(out start); + bool gotEnd = bundle.GetArgument(3, "End").TryGetValueAsInteger(out end); + if (gotStart) { + if (gotEnd && (start == end)) { + return new DreamValue(bundle.ObjectTree.CreateList([text])); + } + else if (start == 0) { + return new DreamValue(bundle.ObjectTree.CreateList()); + } start -= 1; //1-indexed - if(bundle.GetArgument(3, "End").TryGetValueAsInteger(out end)) + } + + if(gotEnd) { if(end == 0) end = text.Length; else end -= 1; //1-indexed + } + bool includeDelimiters = false; if(bundle.GetArgument(4, "include_delimiters").TryGetValueAsInteger(out var includeDelimitersInt)) includeDelimiters = includeDelimitersInt != 0; //idk why BYOND doesn't just use truthiness, but it doesn't, so... @@ -2620,51 +2651,92 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj string endText = text[Math.Min(end, text.Length)..]; // figure out if we should be adding the start/end in the first place + string cutText; if(start > 0 || end < text.Length) - text = text[Math.Max(start,0)..Math.Min(end, text.Length)]; + cutText = text[Math.Max(start,0)..Math.Min(end, text.Length)]; + else + cutText = text; - var delim = bundle.GetArgument(1, "Delimiter"); //can either be a regex or string + // Actual special handling for empty str Delimiters + if (isEmptyString && !isRegex) { + if (includeDelimiters) { + var values = new List(); + bool excludeLastDelimiter = end >= text.Length; + for (var i = 0; i < cutText.Length; i++) { + values.Add(cutText[i].ToString()); + values.Add(""); + } + if (!excludeLastDelimiter) { + values.Add(endText); + } + else { + values.Pop(); + } + string[] finalValues = values.ToArray(); + finalValues[0] = startText + finalValues[0]; + if (excludeLastDelimiter) + finalValues[finalValues.Length-1] = finalValues[finalValues.Length-1] + endText; + return new DreamValue(bundle.ObjectTree.CreateList(finalValues.ToArray())); + } + else { + // 'Easy' solution if we don't need the delimiters. + // Weird because of the way the start text is prepended to the 1st and end text is appened as a new element in DM + IEnumerable values = cutText.Select(x => new string(x, 1)); + if (end < text.Length) + values.Append(""); + values.Append(endText); + string[] finalValues = values.ToArray(); + finalValues[0] = startText + finalValues[0]; + return new DreamValue(bundle.ObjectTree.CreateList(finalValues)); + } + } - if (delim.TryGetValueAsDreamObject(out var regexObject)) { + // Delimiter is regex/not empty, handle accordingly + if (regexObject is DreamObjectRegex) { if(includeDelimiters) { var values = new List(); int pos = 0; - foreach (Match m in regexObject.Regex.Matches(text)) { - values.Add(text.Substring(pos, m.Index - pos)); - values.Add(m.Value); + foreach (Match m in regexObject.Regex.Matches(cutText)) { + string subStr = cutText.Substring(pos, m.Index - pos); + // First pair of subStr/m.Value must not be included if both are empty + if (!string.IsNullOrEmpty(subStr)) + values.Add(subStr); + values.Add(m.Value); pos = m.Index + m.Length; } - values.Add(text.Substring(pos)); - string[] arrayValues = values.ToArray(); - arrayValues[0] = startText + arrayValues[0]; - arrayValues[arrayValues.Length-1] = arrayValues[arrayValues.Length-1] + endText; - return new DreamValue(bundle.ObjectTree.CreateList(arrayValues)); + if (string.IsNullOrEmpty(values.First())) + values.RemoveAt(0); + if (string.IsNullOrEmpty(values.Last())) + values.Pop(); + string[] finalValues = values.ToArray(); + return new DreamValue(bundle.ObjectTree.CreateList(finalValues)); } else { - string[] values = regexObject.Regex.Split(text); + string[] values = regexObject.Regex.Split(cutText); + // Weird DM behavior: Zero-width splits dont include matches for the leftmost side + if (isEmptyString) + values = values[1..]; values[0] = startText + values[0]; values[values.Length-1] = values[values.Length-1] + endText; return new DreamValue(bundle.ObjectTree.CreateList(values)); } - } else if (delim.TryGetValueAsString(out var delimiter)) { + } else { string[] splitText; if(includeDelimiters) { //basically split on delimiter, and then add the delimiter back in after each split (except the last one) - splitText = text.Split(delimiter); + splitText = cutText.Split(delimiterString); string[] longerSplitText = new string[splitText.Length * 2 - 1]; for(int i = 0; i < splitText.Length; i++) { longerSplitText[i * 2] = splitText[i]; if(i < splitText.Length - 1) - longerSplitText[i * 2 + 1] = delimiter; + longerSplitText[i * 2 + 1] = delimiterString; } splitText = longerSplitText; } else { - splitText = text.Split(delimiter); + splitText = cutText.Split(delimiterString); } splitText[0] = startText + splitText[0]; splitText[splitText.Length-1] = splitText[splitText.Length-1] + endText; return new DreamValue(bundle.ObjectTree.CreateList(splitText)); - } else { - return new DreamValue(bundle.ObjectTree.CreateList()); } } From 667e5f3fc5bc094494f0b6e392a2ed1f4b0e3df7 Mon Sep 17 00:00:00 2001 From: Romayne <27376947+MeggalBozale@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:46:01 -0400 Subject: [PATCH 5/5] mb --- OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 5237604f08..be6c4ace91 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -2704,10 +2704,14 @@ public static DreamValue NativeProc_splittext(NativeProc.Bundle bundle, DreamObj values.Add(m.Value); pos = m.Index + m.Length; } + // Trim zero-width matches at start/end because BYOND if (string.IsNullOrEmpty(values.First())) values.RemoveAt(0); if (string.IsNullOrEmpty(values.Last())) values.Pop(); + // Glue back together afterwards (Order is important) + values.Insert(0, startText); + values.Add(endText); string[] finalValues = values.ToArray(); return new DreamValue(bundle.ObjectTree.CreateList(finalValues)); } else {