How to use expr inside if statement? - if-statement

I have not managed to understand how to embed expr in other constructs.
I can easily type
set i {1 2 3}
expr {[llength $i]} #result is 3
However after long research I have not managed to find a way to put that inside if
if {"magic tcl code with expr and llength==3 required here"} {
puts "length is 3"
}

The first argument to if is an expression, just as the argument to expr is an expression.
if {[llength $i] == 3} {
puts "length is 3"
}
You can indeed put expr inside an expression using [brackets], just as with any command, but there's usually no reason to do so; it just makes everything more verbose.
if {[ expr {[llength $i]} ] == 3} {
puts "length is 3"
}
The exception to the above rule comes when you've got an expression that's somehow dynamic; that's when putting an expr inside an expression makes sense as it allows the outer parts to be bytecode-compiled efficiently.
# Trivial example to demonstrate
set myexpression {[llength $i]}
if {[expr $myexpression] == 3} {
puts "evaluated $myexpression to get three"
}
That's pretty rare.

The if command has a built-in expr. Specifically both if and expr expects and expression as the first argument:
if expression ...
expr expression
Therefore, if you want to execute expr {[llength $i]} then just do:
if {[llength $i]} {
}
But to tell you the truth, I don't believe you want to execute
expr {[llength $i]}
I think what you want is to check if the length is 3, therefore what you really want is
expr {[llength $i] == 3}
If that is the case, then the if version would be:
if {[llength $i] == 3} {
puts "llength is 3"
}

Related

Regular expression is too complex error in tcl

I have not seen this error for a small list. Issue popped up when the list went >10k. Is there any limit on the number of regex patterns in tcl?
puts "#LEVELSHIFTER_TEMPLATES_LIMITSFILE:$perc_limit(levelshifter_templates)"
puts "#length of templates is :[llength $perc_limit(levelshifter_templates)]"
if { [regexp [join $perc_limit(levelshifter_templates) |] $temp] }
#LEVELSHIFTER_TEMPLATES_LIMITSFILE:HDPELT06_LVLDBUF_CAQDP_1 HDPELT06_LVLDBUF_CAQDPNRBY2_1 HDPELT06_LVLDBUF_CAQDP_1....
#length of templates is :13520
ERROR: couldn't compile regular expression pattern: regular expression is too complex
If $temp is a single word and you're really just doing a literal test, you should invert the check. One of the easiest ways might be:
if {$temp in $perc_limit(levelshifter_templates)} {
# ...
}
But if you're doing that a lot (well, more than a small number of times, 3 or 4 say) then building a dictionary for this might be best:
# A one-off cost
foreach key $perc_limit(levelshifter_templates) {
# Value is arbitrary
dict set perc_limit_keys $key 1
}
# This is now very cheap
if {[dict exists $perc_limit_keys $temp]} {
# ...
}
If you've got multiple words in $temp, split and check (using the second technique, which is now definitely worthwhile). This is where having a helper procedure can be a good plan.
proc anyWordIn {inputString keyDictionary} {
foreach word [split $inputString] {
if {[dict exists $keyDictionary $word]} {
return true
}
}
return false
}
if {[anyWordIn $temp $perc_limit_keys]} {
# ...
}
Assuming you want to see if the value in temp is an exact match for one of the elements of the list in perf_limit(levelshifter_templates), here's a few ways that are better than trying to use regular expressions:
Using lsearch`:
# Sort the list after populating it so we can do an efficient binary search
set perf_limit(levelshifter_templates) [lsort $perf_limit(levelshifter_templates)]
# ...
# See if the value in temp exists in the list
if {[lsearch -sorted $perf_limit(levelshifter_templates) $temp] >= 0} {
# ...
}
Storing the elements of the list in a dict (or array if you prefer) ahead of time for an O(1) lookup:
foreach item $perf_limit(levelshifter_templates) {
dict set lookup $item 1
}
# ...
if {[dict exists $lookup $temp]} {
# ...
}
I found a simple workaround for this problem by using a foreach statement to loop over all the regexes in the list instead of joining them and searching, which failed for a super-long list.
foreach pattern $perc_limit(levelshifter_templates) {
if { [regexp $pattern $temp]}
#puts "$fullpath: [is_std_cell_dev $dev]"
puts "##matches: $pattern return 0"
return 0
}
}

How to use foreach inside subst in Tcl (template iteration)?

Does anyone know how if there is a way to include a foreach loop in the subst command, to get a pseudo-template effect?
For example, the following works:
set lim 3
set table sldkfjsl
set sqlpat {
select * from $table limit $lim
}
set sqltext [subst $sqlpat]
But I would like to do something like
set sqlpat {
foreach i {1 2 3} {
select * from ${table}_$i limit $lim;
}
}
set sqltext [subst $sqlpat]
And have it give three separate lines of sql:
select * from sldkfjsl_1 limit 3
select * from sldkfjsl_2 limit 3
select * from sldkfjsl_3 limit 3
Any ideas? Thanks!
(EDIT, my solution which sort of shows how build a strfor command that can be used in a subst template, in my case for passing both SQL and gnuplot code to their respective programs):
proc strfor { nms vals str } {
set outstr ""
foreach $nms $vals {
append outstr [subst $str]
}
return $outstr
}
set foostr1 {select $a from table_$b;\n}
set x [strfor {a b} {A 1 B 2 C 3 D 4} $foostr1]
set foostr2 {
blahsd line 1
blahg line 2
[strfor {a b} {A 1 B 2 C 3 D 4} {
forline1 $a $b
forline2 $b $a
}]
blah later
}
puts [subst $foostr2]
The looping commands in Tcl do not return values, so they are useless in a string which is processed with subst. It is of course possible to write an accumulating looping command as you have done. Another possibility is to use lmap. However, the problem can be solved in an easier way.
set lim 3
set table sldkfjsl
We're going to make a list where every item is an instance of a literal template with variable substitutions. First we create an empty list:
set sqlpats {}
Then we loop for each value in the sequence 1..3. For every iteration we append an instance of the template to the list:
foreach i {1 2 3} {
lappend sqlpats "select * from ${table}_$i limit $lim"
}
(subst isn't necessary here, ordinary variable substitution is sufficient.)
Create a resulting string from the list, with newlines between each item (yep, I was wrong, one more command was needed):
join $sqlpats \n
ETA:
subst is one of those commands which is nice to have, but that I for one almost never use. For most purposes, simpler measures will do. Once in a while though, a convoluted bit of code leaves a string unsubstituted. I pick up subst out of the drawer and zap! That said, the ability to selectively allow or disallow different kinds of substitutions alone makes subst very worthwhile.
Documentation: foreach, join, lappend, lmap, set, subst

Map strange behaviour

I have a map function as follows, which reads from an array of lines generated by a unix command.
my %versions = map {
if (m/(?|(?:^Patch\s(?(?=description).*?(\w+)\sPATCH).*?(\d+(?:\.\d+)+).*)|(?:^(OPatch)\s(?=version).*?(\d+(\.\d+)+)))/)
{ 'hello' => 'bye'; }
} #dbnode_versions;
print Dumper(\%versions); gives
$VAR1 = {
'' => undef,
'hello' => 'bye',
'bye' => ''
};
which I find extremely odd, as the hello and bye values should only get added if the regex is true. Anyone able to help me out?
Well, you have to consider what happens when the regex doesn't match, and the if is false. The if will evaluate to some value, although you shouldn't rely on the value of a statement.
Especially, if (cond) { expression } is roughly equivalent to cond and expression. This means that if the regex (our cond) will not match, we'll get a false value.
use Data::Dump;
dd [map { /foo(bar)/ and (hello => 'bye') } qw/foo foobar bar/];
What is your expected output? You may have thought ["hello", "bye"]. But actually, we get
["", "hello", "bye", ""]
because "" represents the false value returned by the regex match on failure.
If you want to return nothing in failure cases, you should explicitly return an empty list:
map { /foo(bar)/ ? (hello => 'bye') : () } qw/foo foobar bar/
or use grep, which filters a list for those elements that match a condition:
my %hash =
map { hello => 'bye' } # replace each matching element
grep { /foo(bar)/ } # filter for matching elements
qw/foo foobar bar/;
The %hash will them either be () or (hello => 'bye'), as each key can only occur once.

How to match the variable in switch with contents of a list?

I have a doubt concerning the use of switch in tcl. Mainly, I was wondering if it was possible to make something like:
switch myvar {
list1 {
puts "myvar matches contents of list1"; }
list2 {
puts "myvar matches contents of list2"; }
default {
puts "myvar doesn't match any content of any list"; }
}
In here, list1 and list2 would be either a list or array of strings containing the names of different files.
Is this even possible without making a very detailed regexp search?
Thanks!
You can rewrite it as an if elseif else construct easily, as Brian Fenton already said (and simplify it with the 'in' operator too.
if {$myvar in $list1} {
puts "myvar matches content of list"
} elseif {$myvar in $list2} {
puts "myvar matches content of list2"
} elseif {
puts "myvar doesn't match any content of any list"
}
You could of course wrap up the code and write your own switch version that does what you want, after all, this is Tcl...
proc listswitch {item conditions} {
if {[llength $conditions] % 2} {
return -code error "Conditions must be pairs"
}
set code ""
foreach {cond block} $conditions {
if {$cond eq "default"} {
set code $block
break
} elseif {$item in $cond} {
set code $block
break
}
}
if {$code ne ""} {
uplevel 1 $code
}
}
listswitch 10 {
{10 20 30 50} {
puts "Match in list 1" }
{50 20 90 11} {
puts "Match in list 2"
}
default {
puts "No match"
}
}
You need to worry a little if you want to match filenames literally, or what kind of equality your interested in though. There are some subtle things there, like case insensitive filesystems, different directory separators, absolute vs. relative and even stuff like filesystem encodings which might change the outcome.
Nice question Jason. At first, I thought you wanted a way to compare the contents of two lists. But I think you want to check if the string is a member of the lists. I don't see any easy way to do that with switch, so what I would do is very simply to use lsearch.
if {[lsearch $list1 $myvar ] != -1} {
puts "myvar matches contents of list1"; }
} elseif {[lsearch $list2 $myvar ] != -1} {
puts "myvar matches contents of list2"; }
} else
puts "myvar doesn't match any content of any list"; }
}

TCL Regular Expression Doubt

As per my understanding of RE
--> * means matches 0 or more occurrences of prev regex
--> + means matches 1 or more occurrences of prev regex
Now lets take a look at the following examples
FIRST:-
% regexp {:+} "DHCP:Enabled" first
1
% puts $first
: --> ":" is stored in variable first
%
SECOND:-
% regexp {:*} "DHCP:Enabled" sec
1
% puts $sec
--> Nothing is stored in variable second
%
Why is ":" stored for the FIRST one and not the SECOND?
The second regexp {:*} matches the empty string because the empty string is 0 occurrences of :. If you use the -indices option for regexp, you'll see that it matches at position 0.
% regexp -indices :* "DHCP:Enabled" indices
1
% puts $indices
0 -1
In other words, the regexp matches at the first character and returns.
It matches the empty string so that it can
match that empty string at the start of “DHCP:Enabled”. The regular
expression engine like to match things up as soon as possible. To show, here's an interactive session:
% regexp -inline {:*} "DHCP:Enabled"
{}
% regexp -inline -all {:*} "DHCP:Enabled"
{} {} {} {} : {} {} {} {} {} {} {}
% regexp -inline -indices -all {:*} "DHCP:Enabled"
{0 -1} {1 0} {2 1} {3 2} {4 4} {5 4} {6 5} {7 6} {8 7} {9 8} {10 9} {11 10}
The -inline option is useful for simple testing, the -all matches in
every matchable location instead of just the first, and the -indices
returns locations rather than the string.
Note that only once (4 4) is the end at least at the same index as the start; in all other cases, an empty string matches (and it's legal; you said that matching nothing was OK).
In general, it's a really good idea to make sure that your overall RE cannot match the empty string or you'll be surprised by the results.