0
\$\begingroup\$

The way to find and replace text in javascript from regex is like so:

"ab".replace(/./g, (match) => match + "&");
// outputs: a&b&

What is the equivalent function or functions in gdscript?

\$\endgroup\$
3
  • \$\begingroup\$ When I search for "godot regex", the first result is this page from the engine documentation, which includes information on how to replace text with the sub method. What do you need that isn't already documented there? \$\endgroup\$
    – DMGregory
    Commented Jul 21, 2023 at 14:44
  • \$\begingroup\$ The "replace" argument of sub is a string. I suppose I could achieve a lot of things by using capture groups, but there's also a lot of things I couldn't achieve. In my case, I need to know what was captured and add it to a dictionary. I'm doing more than string transformations. \$\endgroup\$
    – Seph Reed
    Commented Jul 21, 2023 at 21:01
  • 1
    \$\begingroup\$ That would be a good thing to state in your question, then. Use the "edit" link at the bottom to ensure all relevant information is included. \$\endgroup\$
    – DMGregory
    Commented Jul 21, 2023 at 21:29

2 Answers 2

2
\$\begingroup\$

In Godot we use the RegEx class, and it has a sub method to replace matches, however it does not take a callback. Callables are relatively recent addition in Godot, and many APIs has not been updated to use them. That does not mean we can't do it.

Furthermore, we cannot specify global (g) when compiling the RegEx (Godot does not support the delimiter and modifier syntax). However the g modifier means it will not stop on one match... Which is what search_all will do. So we build our code on top of it.

The method search_all returns an Array of RegExMatch from which we can get the start and end indexes of each match... To replace the text we can use get_slice and build a new String from the pieces (No, Godot does not have an equivalent of splice). And since the replacement might not have the same length, the indexes would be wrong after the first replacement... So, we will keep track of an offset to make it work.

This is working Godot 4 code:

var subject := "ab"
var pattern := "."
var callback := func(text:String) -> String: return text + "&"

var regex := RegEx.new()
regex.compile(pattern)

var regex_matches := regex.search_all(subject)
var offset := 0
for regex_match in regex_matches:
    var start := regex_match.get_start()
    var end := regex_match.get_end()
    var length := end - start
    var text := subject.substr(start + offset, length)
    var replacement := str(callback.call(text))
    subject = (
        subject.substr(0, start + offset)
        + replacement
        + subject.substr(end + offset)
    )
    offset += replacement.length() - text.length()

prints(subject)

Output:

a&b&

For Godot 3, we cannot write an inline anonymous method like I did for callback above. And Godot 3 didn't have typed Arrays, so we cannot infer the type of regex_match thus some implicit typing won't work (I mean you would have to use = instead of :=).


Addendum:

For your case, you could use a capture group in your pattern (i.e. "(.)") so you can use a backreference in sub. Which results in less code - assuming you can express the replacement you want in this form.

This is working code (it should work in both Godot 3 and Godot 4):

var subject := "ab"
var pattern := "(.)"

var regex = RegEx.new()
regex.compile(pattern)

subject = regex.sub(subject, "$1&", true)

prints(subject)

Output:

a&b&

Note that I'm passing true as third parameter of sub, that is telling it to replace all matches, without it the output is a&b (i.e. it only replaces the first match).

\$\endgroup\$
0
2
\$\begingroup\$

Based off the awesome answer by @Theraot, here's a utility function which can be added to an autoload script.

func str_replace(target: String, regex: RegEx, cb: Callable) -> String:
    var out = ""
    var last_pos = 0
    for regex_match in regex.search_all(target):
        var start := regex_match.get_start()
        out += target.substr(last_pos, start - last_pos)
        out += str(cb.call(regex_match.get_string()))
        last_pos = regex_match.get_end()
        
    out += target.substr(last_pos)
    return out

Usage:

# outputs: "alad"
print(Globals.str_replace(
    "abcd", 
    RegEx.create_from_string("bc"), 
    func(text: String): return "la"
))
\$\endgroup\$
1
  • 1
    \$\begingroup\$ As alternative to using an autoload, you could make it an static func and put on a script with a class_name. \$\endgroup\$
    – Theraot
    Commented Jul 21, 2023 at 22:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .