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
57 changes: 57 additions & 0 deletions OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,63 @@ public static DreamValue HandleOviewersOhearers(NativeProc.Bundle bundle, DreamO
return new DreamValue(view);
}

public static DreamValue HandleReplaceText(DreamValue haystackValue, DreamValue needleValue, DreamValue replacementValue, int start, int end, bool exact) {
// TODO: _char support if/when we support char

if(needleValue.TryGetValueAsDreamObject<DreamObjectRegex>(out var regexObject)) {
// Equivalent to regex.Replace according to spec
return DreamProcNativeRegex.RegexReplace(regexObject, haystackValue, replacementValue, start, end);
}

if(!haystackValue.TryGetValueAsString(out var haystack))
return DreamValue.Null;

if(start == 0)
return new(haystack);

if(start < 0)// Negative wrap-around
start = Math.Max(start + haystack.Length + 1, 1);
if(end <= 0)// Zero or negative wrap-around
end = Math.Max(end + haystack.Length + 1, start);

var replacement = replacementValue.Stringify();

needleValue.TryGetValueAsString(out var needle);
if(string.IsNullOrEmpty(needle)) {
if(replacement.Length == 0) {
return new DreamValue(haystack);
}

if(start == 1) {
start = 2;
}

end = Math.Min(end, haystack.Length);

StringBuilder result = new();
for(int i = 0; i < haystack.Length; i++) {
result.Append(haystack[i]);
if (i >= start - 2 && i < end - 1)
result.Append(replacement);
}

return new DreamValue(result.ToString());
}

// TODO: replacetext automatically upper/lowercases matches, but how? To what extent?
string before = haystack[..(start - 1)];
string after = haystack[(end - 1)..];
string textSub = haystack.Substring(start - 1, end - start);
string replaced = textSub.Replace(needle, replacement, exact ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);

StringBuilder newTextBuilder = new();
newTextBuilder.Append(before);
newTextBuilder.Append(replaced);
newTextBuilder.Append(after);

return new DreamValue(newTextBuilder.ToString());
}

/// <summary>
/// Determines whether the first parameter is "visible" to the second parameter, according to BYOND's various rules on visibility.
/// </summary>
Expand Down
108 changes: 10 additions & 98 deletions OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2063,68 +2063,11 @@ public static DreamValue NativeProc_regex(NativeProc.Bundle bundle, DreamObject?
public static DreamValue NativeProc_replacetext(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) {
DreamValue haystack = bundle.GetArgument(0, "Haystack");
DreamValue needle = bundle.GetArgument(1, "Needle");
DreamValue replacementArg = bundle.GetArgument(2, "Replacement");
bundle.GetArgument(3, "Start").TryGetValueAsInteger(out var start); //1-indexed
int end = bundle.GetArgument(4, "End").GetValueAsInteger(); //1-indexed
DreamValue replacement = bundle.GetArgument(2, "Replacement");
bundle.GetArgument(3, "Start").TryGetValueAsInteger(out var start);
bundle.GetArgument(4, "End").TryGetValueAsInteger(out var end);

if (needle.TryGetValueAsDreamObject<DreamObjectRegex>(out var regexObject)) {
// According to the docs, this is the same as /regex.Replace()
return DreamProcNativeRegex.RegexReplace(regexObject, haystack, replacementArg, start, end);
}

if (!haystack.TryGetValueAsString(out var text)) {
return DreamValue.Null;
}

if (start == 0) { // Return unmodified if Start is 0
return new(text);
} else if (start < 0) { // Negative wrap-around
start = Math.Max(start + text.Length + 1, 1);
}

var arg3 = replacementArg.TryGetValueAsString(out var replacement);

if (end <= 0) { // Zero or negative wrap-around
end = Math.Max(end + text.Length + 1, start);
}

if (needle.IsNull) { // Insert the replacement after each char except the last
if (!arg3) { // No change if no Replacement was given
return new DreamValue(text);
}

// A Start of 2 is the same as 1. This only happens when Needle is null.
if (start == 1)
start = 2;

// End cannot reach the last char
end = Math.Min(end, text.Length);

StringBuilder result = new StringBuilder();
for (int i = 0; i < text.Length; i++) {
result.Append(text[i]);
if (i >= start - 2 && i < end - 1)
result.Append(replacement);
}

return new DreamValue(result.ToString());
}

if (needle.TryGetValueAsString(out var needleStr)) {
string before = text.Substring(0, start - 1);
string after = text.Substring(end - 1);
string textSub = text.Substring(start - 1, end - start);
string replaced = textSub.Replace(needleStr, replacement, StringComparison.OrdinalIgnoreCase);

StringBuilder newTextBuilder = new();
newTextBuilder.Append(before);
newTextBuilder.Append(replaced);
newTextBuilder.Append(after);

return new DreamValue(newTextBuilder.ToString());
}

throw new Exception($"Invalid needle {needle}");
return DreamProcNativeHelpers.HandleReplaceText(haystack, needle, replacement, start, end, false);
}

[DreamProc("replacetext_char")]
Expand All @@ -2145,44 +2088,13 @@ public static DreamValue NativeProc_replacetext_char(NativeProc.Bundle bundle, D
[DreamProcParameter("Start", Type = DreamValueTypeFlag.Float, DefaultValue = 1)]
[DreamProcParameter("End", Type = DreamValueTypeFlag.Float, DefaultValue = 0)]
public static DreamValue NativeProc_replacetextEx(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) {
if (!bundle.GetArgument(0, "Haystack").TryGetValueAsString(out var text)) {
return DreamValue.Null;
}

var arg3 = bundle.GetArgument(2, "Replacement").TryGetValueAsString(out var replacement);

if (!bundle.GetArgument(1, "Needle").TryGetValueAsString(out var needle)) {
if (!arg3) {
return new DreamValue(text);
}

//Insert the replacement after each char except the last char
//TODO: Properly support non-default start/end values
StringBuilder result = new StringBuilder();
var pos = 0;
while (pos + 1 <= text.Length) {
result.Append(text[pos]).Append(arg3);
pos += 1;
}

result.Append(text[pos]);
return new DreamValue(result.ToString());
}

int start = bundle.GetArgument(3, "Start").GetValueAsInteger(); //1-indexed
int end = bundle.GetArgument(4, "End").GetValueAsInteger(); //1-indexed

if (start == 0) { // Return unmodified
return new(text);
} else if (start < 0) { // Negative wrap-around
start = Math.Max(start + text.Length + 1, 1);
}

if (end <= 0) { // Zero and negative wrap-around
end = Math.Max(end + text.Length + 1, start);
}
DreamValue haystack = bundle.GetArgument(0, "Haystack");
DreamValue needle = bundle.GetArgument(1, "Needle");
DreamValue replacement = bundle.GetArgument(2, "Replacement");
bundle.GetArgument(3, "Start").TryGetValueAsInteger(out var start);
bundle.GetArgument(4, "End").TryGetValueAsInteger(out var end);

return new DreamValue(text.Substring(start - 1, end - start).Replace(needle, replacement, StringComparison.Ordinal));
return DreamProcNativeHelpers.HandleReplaceText(haystack, needle, replacement, start, end, true);
}

[DreamProc("replacetextEx_char")]
Expand Down
Loading