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

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.

Related

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

TCL conditional commands using ternary operator

is it possible to run conditional commands using TCL's ternary operator?
using if statement
if {[string index $cVals $index]} {
incr As
} {
incr Bs
}
I would like to use ternary operator as follows but I get an error
invalid command name "1" while executing "[string index $cVals $index]
? incr As : incr Bs"
[string index $cVals $index] ? incr As : incr Bs
For ternary conditions, we should be using boolean values only, either 0 or 1.
So, you can't use string index directly, since it will return either a char or empty string. You have to compare whether the string is empty or not.
Also, the for the pass/fail criteria of conditions, we have to give literal values. You should use expr to evaluate the expressions.
A basic example can be ,
% expr { 0 < 1 ? "PASS" : "FAIL" }
PASS
% expr { 0 > 1 ? "PASS" : "FAIL" }
FAIL
%
Note that I have used double quotes for the string since it has the alphabets. In case of numerals, it does not have to be double quotes. Tcl will interpret numbers appropriately.
% expr { 0 > 1 ? 100 : -200 }
-200
% expr { 0 < 1 ? 100 : -200 }
100
%
Now, what can be done to your problem ?
If you want to use any commands (such as incr in your case), it should be used within square brackets, to mark that as a command.
% set cVals "Stackoverflow"
Stackoverflow
% set index 5
5
% # Index char found. So, the string is not empty.
% # Thus, the variable 'As' is created and updated with value 1
% # That is why we are getting '1' as a result.
% # Try running multiple times, you will get the updated values of 'As'
% expr {[string index $cVals $index] ne {} ? [incr As] : [incr Bs] }
1
% info exists As
1
% set As
1
% # Note that 'Bs' is not created yet...
% info exists Bs
0
%
% # Changing the index now...
% set index 100
100
% # Since the index is not available, we will get empty string.
% # So, our condition fails, thus, it will be increment 'Bs'
% expr {[string index $cVals $index] ne {} ? [incr As] : [incr Bs] }
1
% info exists Bs
1
%

Tcl error in doing sum

I want to compute average on a list of numbers stored in a tcl list P. Here's my script :
set sum 0.0
foreach e $P { set sum [expr {$sum + $e}] }
set avg [expr {1.0*$sum / [llength $P]}]
But I have the error : can't use non-numeric string as operand of "+"
How can I do the sum ?
Your problem is probably due to some element in P not being a number. In any case, this is how you calculate average:
package require math::statistics
::math::statistics::mean $P
assuming P is a list of numbers.
If you have a list of data items and want to know if any of them are unsuitable for expr arithmetic you can do something like this:
foreach n $data {
if {![string is double -strict $n]} {
error "$n is not a number"
}
}
This will report the first non-number. The string is double command recognizes both integers and floating point numbers1. If you leave out the -strict flag, the empty string will be considered a number (expr will still choke on it, though2).
This will give you the sublist of all non-number items in $data:
lmap n $data {
if {![string is double -strict $n]} {set n} continue
}
And this will give you the sublist of all proper-number items in $data:
lmap n $data {
if {[string is double -strict $n]} {set n} continue
}
1 the name "double" indicates that it returns true for any string that can be translated to the C data type double, which refers specifically to storage of a double precision floating point number, (an industry standard for encoding floating-point numbers). If you don't know what that is, you can pretend that it means double as in "both numbers that look like integers and numbers that look like reals".
2 expr will also choke on the value NaN which is a perfectly valid floating point value, it just stands for "not a number".
Documentation: continue, error, expr, foreach, if, lmap, math::statistics package, package, set, string

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

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

can't use non-numeric string as operand of "&" in TCL

if { ($name1 == "john") & ($name2 == "smith") } { puts "hello world" }
i got error:can't use non-numeric string as operand of "&"
I have try :
if { $name1 == "john" & $name2 == "smith" } { puts "hello world" }
if { {$name1 == "john"} & {$name2 == "smith"} } { puts "hello world" }
what i suppose to do?
The expr command in Tcl allows two forms of AND operations: bitwise (using the operator &) and logical (using the operator &&). The bitwise operator only allows integer operands: the logical operator can deal with both boolean and numeric (integer and floating point values; 0 or 0.0 means false in this case) operands. Use the logical AND operator unless you specifically want to work with bit patterns.
An expression like
$foo eq "abc" && $bar eq "def"
works because the eq operators evaluate to boolean values (BTW: prefer the new eq (equal) operator to == if you're making string equality comparisons, as it's more efficient), leaving && with two boolean operands.
The following code, however
{$foo eq "abc"} && {$bar eq "def"}
fails because the braces prevent substitution and forces the && to deal with two string operands. In this case, the && operator gives the error message
expected boolean value but got "$foo eq "abc""
and the & operator gives the message
can't use non-numeric string as operand of "&"
which was what you got.