Why does expr $a eq $b fail with "invalid bareword"? - tcl

I have TCL 8.6 and the following code works fine:
set a abc
set b abcd
if {$a eq $b} {puts hi}
But the following gets me error:
set a abc
set b abcd
expr $a eq $b
invalid bareword "abc"
in expression "abc eq abcd";
should be "$abc" or "{abc}" or "abc(...)" or ...
I wonder what is going on? Isn't that the condition expression in if command same as the expression in expr command?

No, it's not the same. There is a difference between what you see, and what expr sees, i.e. the string that it will try to evaluate. This is because every word of the command invocation (just like with every command invocation in Tcl) is subjected to substitution before the command is executed.
First case: the braces prevent the contents of the expression from being prematurely substituted. You see {$a eq $b}, expr sees $a eq $b. This is two operands ($a and $b) and an operator (eq): expr can work with this.
Second case: the three arguments are substituted before they are passed to expr. You see $a eq $b, expr sees abc eq abcd. That's two nonsense values and an operator, and expr can't deal with that.
If there is a string that isn't a boolean value or the name of an operator in an expr expression, it should be part of a variable substitution ($abc), or the name of a function (abc(...)), or part of a command substitution ([... abc ...]), or else be explicitly quoted ("abc" or {abc}).
Always brace the arguments to expr. Doing so prevents many problems, this one being one of the milder.
Documentation: expr

Related

Variable substitution in TCL within curly brace

I'm having a problem where variables are in curly brace.I am trying to perform a variable substitution within curly braces which I understand is not feasible directly in TCL.But if there are other methods to resolve this? because I see the samiliar question in website that the answer is use list [] and others. But I want to countinue use curly brace, could someone can help me to resolve the question?
set top_design a
puts $top_design
puts {aaa %top_design}
output :
a
aaa %top_design
so how to display the subtitute of top_design in second puts.
Aside from the % typo, you're looking for the subst command:
set top_design a
puts {aaa $top_design} ;# => aaa $top_design
puts [subst {aaa $top_design}] ;# => aaa a
There are options to subst so that you have control over which things get substituted:
% puts [subst {aaa $top_design\n[clock seconds]}]
aaa a
1666273294
% puts [subst -nocommands -nobackslashes {aaa $top_design\n[clock seconds]}]
aaa a\n[clock seconds]
I think that you have messed up a little while forming the question.
In Tcl, { and } have special meaning, you can simpy say that this is an escape sequence or a list - depending on where it is used.
Let's see the following simple example:
set v1 aaa
set v2 bbb
set vres1 {$v1 $v2}
set vres2 "\{$v1 $v2\}"
set vres3 "\{\$v1 \$v2\}"
set vres4 {{$v1 $v2}}
This will result in the following output:
% set vres1 {$v1 $v2}
$v1 $v2
% set vres2 "\{$v1 $v2\}"
{aaa bbb}
% set vres3 "\{\$v1 \$v2\}"
{$v1 $v2}
% set vres4 {{$v1 $v2}}
{$v1 $v2}
For vres1, you have variables not evaluated as {} were used as an escape sequence.
For vres2, braces are escaped so are treated as a standard character, thus "" are needed to properly set all the characters as one argument for set command. Note that v1 and v2 are evaluated here as {} are not escape characters but characters that we are just saving in variable.
For vres3, we did manual escaping of braces and $ so that variables are not evaluated and braces are part of the string.
For vres4, first set of braces are escaping the second set and also $.
Now I guees, that you already have something like vres3 or vres4 and you want to get the values of the variables in braces.
When you have nested variables, you should use the eval command like below:
% eval set out1 \"$vres1\"
aaa bbb
% eval set out2 \"$vres2\"
{aaa bbb}
% eval set out3 \"$vres3\"
{aaa bbb}
% eval set out4 \"$vres4\"
{aaa bbb}
I hoped it helped in your issue!
[Ufff, my first answer!]
In Tcl 8.7, you'll be able to do this:
set top_design a
puts $top_design
set input {aaa %top_design}
puts [regsub -all -command {%(\w+)} $input {apply {{- varName} {
upvar 1 $varName var
# Ignore case where variable doesn't exist
return $var
}}}]
The key thing is that %top_design isn't very special at all to Tcl code; the % symbol is only meaningful in a few contexts (format, clock, expr, and Tk's bind). For it to have any other meaning, you have to apply that meaning yourself. That gets much easier with regsub -command (a new feature in 8.7) since that lets you use a command to generate the substitution within a regsub; that pairs well with apply though you could use a procedure instead. In earlier versions, such substitutions required non-trivial quoting and subst; I always found those things difficult to write correctly.

Use one list as multi variable, such as format argument

In Tcl
set f "%-3s %-3s %-3s"
set t {"aaa" "bbb" "ccc"}
puts [format $f $t]
I know it's incorrect, and it will return Error:
not enough argument
So how to correct it?
You can to use list expansion (as from Tcl 8.5):
set f "%-3s %-3s %-3s"
set t {"aaa" "bbb" "ccc"}
puts [format $f {*}$t]
In prior versions, you would have to use eval, which is not recommended
puts [eval format \$f $t]
As the number of specifiers and arguments to format is fixed, why not use lassign:
% lassign $t v1 v2 v3
% puts [format $f $v1 $v2 $v3]
aaa bbb ccc
In general, the expansion operator {*} is purposeful when the number of specifiers, and, therefore, arguments is somewhat dynamic. But in the static case, you end up with a more robust script when using lassign. Picture your list in t varying in size, while format expects exactly three value arguments?

What is the magic behind the TCL command expr with the operator eq

I would like to understand the TCL command expr for string comparison:
I tried the following:
expr {t eq t}
=> 1
expr {tru eq tr}
=> 0
expr {tru eq truee}
=> invalid bareword "truee" ...
expr {a eq a}
=> invalid bareword "a" ...
What is the magic behind the words t, tr, tru? Does Tcl special handling for these strings? I learnt that I had to quote the string if I use expr with eq, but I have some legacy programs which use this form of comparison. I would like to understand it. Thanks.
In Tcl, eq and ne does the string comparison while == does the numerical comparison.
% expr {1 == 1.0}
1
% expr {1 eq 1.0}
0
%
Whenever you are using the eq and if the input non-numeric, then it should be used with double quotes or with a variable reference. You can't use the literal bareword string notation.
For e.g
% expr {"a" eq "b"}; # Strings with double quotes
0
% set i a
a
% expr {$i eq "a"}; # String with variable reference
1
% expr {a eq b}; # Literally using the string as bareword
invalid bareword "a"
in expression "a eq b";
should be "$a" or "{a}" or "a(...)" or ...
%
There is a exception in this rule, where the Tcl boolean comes into play.
In Tcl, a proper boolean value is either a proper integer, with, like in C, zero meaning false and non-zero meaning true, or one of the following:
yes, true, on --> Boolean 1
no, false, off --> Boolean 0
When they are used partially, Tcl tends to match with any of the known items and evaluation happens accordingly.
If you evaluate expr with these special words, then it will return the same.
% expr {true}
true
% expr {false}
false
% expr {t}; # Tcl automatically matches internally and map it to 'true'
t
% expr {fa} ; # Similary, mapped to 'false'
fa
% expr {o} ; # Will throw error as it is conflicting with 'on' & 'off'
invalid bareword "o"
in expression "o";
should be "$o" or "{o}" or "o(...)" or ...
% expr {on}
on
% expr {of}; # Matching 'off' boolean
of
% if true {puts ya}
ya
% if n {puts ya}; # Matching boolean 'no'
% if f {puts ya}; # Matching boolean 'false'
% if false {puts ya}
% if yes {puts ya}
ya
% if y {puts ya}; # Matching boolean 'y'
ya
So, If your input is mapping with boolean, they are still valid, but treated as string only.
Now, lets go back to your original question.
% expr {t eq t}; # Tcl mapped the 'true' boolean and string-wise both are same. So, returned 1
1
% expr {tru eq tr}; # Both mapped 'true'. But, string-wise differs. So, returned 0
% expr {tru eq truee}; # 'tru' mapped to 'true' and 'truee' is unknown. So error
invalid bareword "truee"
% expr {a eq a}; # Both are used literally, so error throw.
invalid bareword "a"
Now, your question from the comments,
% expr {yes == true}; # String-wise, both are different. So, returned 0
0

How to define a variable with argument expansion

The following command runs as expected:
lappend {*}{arr 1}
puts [lindex $arr 0]
Now I am trying to make a variable of "{*}{arr 1}" like this:
set X "{*}{arr 1}"
lappend $X
But this does not work, seems $X is taken as one whole value, argument expansion is not effective.
So is it a requirement that argument expansion can not be through variable?
The {*} is a syntactic feature of Tcl (from Tcl 8.5 onwards) just as […], "…" or $ is. You have to write it in the script in order for it to count as argument expansion; otherwise it's just a sequence of three characters.
If you want something like
set X "{*}{arr 1}"
lappend $X
to work, you need to pass it through eval:
set X "{*}{arr 1}"
eval lappend $X
Note that this then means that X actually contains a script fragment; this can have all sort of “interesting” consequences. Try this for size:
set X "{*}{arr 1};puts hiya"
eval lappend $X
Use of eval in modern Tcl is usually a sign that you're going about stuff the wrong way; the key use in old scripts was for doing things similar to that which we'd use {*} for now.
No, within double quotes, { and } actually lose their meaning, so will {*}. Notice that puts "{}" and puts {} are different.
The closest I can think of to do what you're trying to do would be to use something like this:
set X {arr 1}
lappend {*}$X
So if you now execute puts [lindex $arr 0], you get 1 as output.

Why does expr "i==i" fail with "invalid bareword"?

1)
% expr "1==1"
1
2)
% expr "i==i"
invalid bareword "i"
in expression "i==i";
should be "$i" or "{i}" or "i(...)" or ...
Why am getting this error in step - 2
1) % if {"i" == "i"} {
puts "hai"
}
hai
2) % if {i == "i"} {
puts "hai"
}
invalid bareword "i"
in expression "i == "i"";
should be "$i" or "{i}" or "i(...)" or ...
if {"i" == "i"} This is wotking with if condition .
Here I found like expr command evaluating only integers , not comparing strings , But the In "if" condition everything (integer as well as string) are evaluating .
How Things are working here ?
The answer is in the expr man page.
Operands may be specified in any of the following ways:
...
[4] As a string enclosed in double-quotes. The expression parser
will perform backslash, variable, and command substitutions on
the information between the quotes, and use the resulting value
as the operand
[5] As a string enclosed in braces. The characters between the open
brace and matching close brace will be used as the operand with‐
out any substitutions.
...
So, expr can compare strings, but you must enclose them in double quotes or curly braces, depending on whether you want substituions performed or not.
Therefore, in your example 2, you must use
% expr {"i" == "i"}
or
% expr {{i} == {i}}
Better to user the string comparison operands:
% expr {"i" eq "i"}
% expr {{i} eq {i}}
to be sure that the content of the string is not converted to numerical values.
In Tcl 8.4
you can use
%expr {"i" == "i"}
or
%expr ( "i" == "i" )
Both syntaxes will work.