Search code examples
regextcl

Is there a way to do multiple substitutions using regsub?


Is it possible to have do different substitutions in an expression using regsub?

example:

set a ".a/b.c..d/e/f//g"

Now, in this expression, is it possible to substitute "." as "yes" ".." as "no" "/" as "true" "//" as "false" in a single regsub command?


Solution

  • With a regsub, no. There's a long-standing feature request for this sort of thing (which requires substitution with the result of evaluating a command on the match information) but it's not been acted on to date.

    But you can use string map to do what you want in this case:

    set a ".a/b.c..d/e/f//g"
    set b [string map {".." "no" "." "yes" "//" "false" "/" "true"} $a]
    puts "changed $a to $b"
    # changed .a/b.c..d/e/f//g to yesatruebyescnodtrueetrueffalseg
    

    Note that when building the map, if any from-value is a prefix of another, the longer from-value should be put first. (This is because the string map implementation checks which change to make in the order you list them in…)


    It's possible to use regsub and subst to do multiple-target replacements in a two-step process, but I don't advise it for anything other than very complex cases! A nice string map is far easier to work with.


    EDIT: In Tcl 9.0, you can do this with regsub -command:

    regsub -all -command  {\.\.?|//?} $a {
        dict get {. yes .. no / true // false}
    }
    

    The inner selection of what to replace with is via dict get as the neatest way to do this, but in general you're more likely to use apply and a lambda expression like this:

    regsub -all -command  {\.\.?|//?} $a {apply {{substring} {
        const MAPPING {. yes .. no / true // false}
        return [dict get $MAPPING $substring]
    }}}
    

    Using a lambda lets you do much more complex transformations.

    The argument passed to the inner command call is the substring that is matched (plus any capturing sub-REs as extra arguments), and the result of the inner command call is the substitution to use for the match.