HexedAgain
HexedAgain

Reputation: 1031

tcl v8.5 regex to bind elements to string

The Problem: I am trying to convert

"%0 %1 ... %n" 

to

"[lindex $someList 0] [lindex $someList 1] ... [lindex $someList n]"

with the regex

 regsub -all "(%\c*)" $string "\[lindex \$someList \0\]" string

and it isn't working - I get, instead:

 "[lindex $someList ]0 [lindex $someList ]1 ... [lindex $someList ]n"

The Context:
Essentially I am trying to write a function that will perform some action over all permutations of a given set of lists (or in other words their cartesian product), for example if I had the following:

set action "puts \"%1 hello %0 world\""

and a set of lists

[1 2 3] [a b]

then on invocation of

foreach_n $action [list 1 2 3] [list a b]

I expect the outcome

a hello 1 world
b hello 1 world
a hello 2 world
b hello 2 world
a hello 3 world
b hello 3 world

The functions I have written to do this (haven't checked the edge cases fully yet) are:

proc foreach_n { action args } {

    foreach el [lindex $args 0] {
        foreach_n_helper $action [list $el] {*}[lrange $args 1 end]
    }
}

proc foreach_n_helper { action fixed_elem_values args } {

    foreach el [lindex $args 0] {
        set fixed_vals [list {*}$fixed_elem_values $el]
        if {[llength $args] > 1} {
            foreach_n_helper $action $fixed_vals {*}[lrange $args 1 end]
        } else {

            #I simply cannot get this bit correct
            regsub -all "(%\c*)" $action "\[lindex \$fixed_vals \0\]" action
            puts "$fixed_vals"
            puts $action
            #eval $action
        }
    }
}

and by running

foreach_n $action [list 1 2 3] [list a b]

I see that the statements I would want to eval for each permutation are:

1 a
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
1 b
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
2 a
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
2 b
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
3 a
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"
3 b
puts "[lindex $fixed_vals ]1 hello [lindex $fixed_vals ]0 world"

So the recursion is as I want it, but the whole thing would crash if I actually tried to eval them since the index is outside of [lindex ...] As per the document at https://www.tcl.tk/man/tcl8.5/TclCmd/regsub.htm I tried bracing the subSpec "[lindex \$fixed_vals \0]" and the output was

1 a
puts ""\[lindex \$fixed_vals %\]"1 hello "\[lindex \$fixed_vals %\]"0 world"
1 b
puts ""\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"1 hello "\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"0 world"
2 a
puts ""\[lindex \$fixed_vals %\]"1 hello "\[lindex \$fixed_vals %\]"0 world"
2 b
puts ""\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"1 hello "\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"0 world"
3 a
puts ""\[lindex \$fixed_vals %\]"1 hello "\[lindex \$fixed_vals %\]"0 world"
3 b
puts ""\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"1 hello "\[lindex \$fixed_vals "\[lindex \$fixed_vals %\]"\]"0 world"

which presumably means I am misinterpreting what was stated in the document (or otherwise ...). I have also tried just "random" twiddling and have gotten nowhere.

Can anyone help? And if there is a better way of accomplishing my goal here I would be happy to hear it.

Note: I realize I could achieve my objective by employing the following pattern:

foreach el1 $list1 {
    foreach el2 $list2 {
        .
        .
        .
                  ...    foreach eln $listn {
                             call some function taking n parameters
                         }
                      }
                 ...
     }
 }

but this is precisely the pattern I want to avoid!

Upvotes: 1

Views: 91

Answers (2)

Peter Lewerin
Peter Lewerin

Reputation: 13252

If you are producing a string in one procedure and then want to substitute tokens in that string with generated strings, a regular expression is possibly the least practical way to do so.

Converting the string "%0 %1 ... %n" to "[lindex $someList 0] [lindex $someList 1] ... [lindex $someList n]" is fairly straightforward as the resulting string can be constructed from the input string:

foreach group "%0 %1 %2 %3" {
    lappend res "\[lindex \$somelist [string range $group 1 end]]"
}
join $res

If you want the contents of the input string to select items from a data list it's even simpler:

set data [list foo bar baz qux]
set input "%0 %3 %1 %2 %1"
# use lmap in Tcl 8.6
foreach i [lsort -unique $input] d $data {lappend map $i $d}
string map $map $input
# => foo qux bar baz bar

And so on. Everything is already in place in Tcl, there's no need to bring in regular expressions.

Documentation: foreach, join, lappend, lindex, lsort, set, string

Upvotes: 1

glenn jackman
glenn jackman

Reputation: 246799

I didn't read every detail. Are you missing the subst command?

% set string "%0 %1 ... %3" 
%0 %1 ... %3
% set new [regsub -all {%(\d+)} $string {[lindex $someList \1]}]
[lindex $someList 0] [lindex $someList 1] ... [lindex $someList 3]
% set someList {first second third fourth}
first second third fourth
% subst $new
first second ... fourth

Upvotes: 2

Related Questions