Converting string into mathematical function in Tcl - regex

Let's say I have a string, maybe "Enable && Signal" for simplicity's sake.
I'd like to convert this string to standard && operations in tcl, such that Enable && Signal would return 0 if any of the value is false and 1 only when both are true.
Is there an easy way to do this, As for my case i would need a generic method where the number of arguments can be any and perform logical/relational operations like && || == <= > != etc
Any help and insights would be very much appreciated.
Thanks
I initially tried to split the arguments into conditions list and data list but could not handled the precedence of operations. Like == need to be done first and later && operations for n^n combinations

I'm assuming that in your example, Enable and Signal are Tcl variables. So, all that would be needed to be able to pass the string to expr is to prepend a '$' to all identifiers. That can be done with regsub as follows:
set str "Enable && Signal"
regsub -all {\m[A-Za-z]\w*\M} $str {$&} expr
set result [expr $expr]
Due to the \m\M, This will properly leave numbers like 1e3 alone. But this method falls short if you also want to be able to use functions, like sin(x). If that is also a requirement, a negative lookahead may help:
set str "sin(x) * cos(y)"
regsub -all {\m[A-Za-z]\w*\M(?!\()} $str {$&} expr
puts $expr
This produces: sin($x) * cos($y)

Related

Pass variable to the command with regular expression in Tcl

I am trying to pass the variable to my regular expression to be used while looping through a list of strings. For example, I have a string which is:
top/inst/name[i], where i can take different values of integers.
for {set i 0} {$i < $rows} {set i [expr {$i + 1}]} {
my_command { top/inst/name[$i] top_o/inst_o/name[$i] }
}
How do I tell regular expression parser to treat $i as a number? It complains that $i is a command.
The issue is that […] is serving two different purposes here, one in base Tcl as command substitution syntax, and one for regular expressions as character set syntax. I'm not sure that you want either of them at this point, given that the brackets appear to be part of the actual name of something. So you need to be careful.
To avoid the command substitution, you can either insert \ characters before the [ and the ], or you can use the extended capabilities of subst:
my_command [subst -nocommands { top/inst/name[$i] top_o/inst_o/name[$i] }]
To avoid the other problem, you can either insert more backslashes (note that this can make things ugly after a while) or if you are really using regular expressions to just match a literal (sub)string, you can prefix the regular expression with ***=.
It is idiomatic to use incr i instead of set i [expr {$i + 1}] in for loop iteration clauses. It does the same thing, but is shorter and clearer for (human) readers. It's just like using ++i instead of i = i + 1 in C or C++ (or many other languages).

Tcl switch statement and -regexp quoting?

There must be an extra level of evaluation when using the Tcl switch command. Here's an example session at the repl to show what I mean:
$ tclsh
% regexp {^\s*foo} " foo"
1
% regexp {^\\s*foo} " foo"
0
% switch -regexp " foo" {^\\s*foo {puts "match"}}
match
% switch -regexp " foo" {{^\s*foo} {puts "match"}}
match
...There needed to be an extra backslash added inside the first "switch" version. This is consistent between 8.5.0 and 8.6.0 on Windows. Can someone point me to the section of the manual where this and similar instances of extra levels of unquoting are described? My first thought was that the curly brackets in the first "switch" version would have protected the backslash, but "switch" itself must be applying an extra level of backslash susbtitution to the patterns. Unless I'm misunderstanding the nuances of something else.
Edit:
...hmmm... Like Johannes Kuhn says below backslash substitution apparently depends on the dynamic context of use, and not the lexical context of creation...
% set t {\s*foo}
\s*foo
% proc test_s x {set x}
% test_s $t
\s*foo
% proc test_l x {lindex $x 0}
% test_l $t
s*foo
% puts $t
^\s*foo
...that seems to be quite the interesting design choice.
The problem you describe here is simple to solve:
The difference between switch and regexp is that switch takes actually a list.
So if we print the first element of the list {^\s*foo {puts "match"}} with
% puts [lindex {^\s*foo {puts "match"}} 0]
^s*foo
it results in something that we don't want.
List constructing is a little bit complex, if you are not sure, use an interactive Tcl shell that constructs one for you with list.
Edit: Indeed, it is an intresting desin choice, but this applies to everything in Tcl. For example expr uses an minilanguage designed for arithmetic expressions. It is up to the command what it shall do with it's arguments. Even language constucts like if, for, while are just commands that treats one of the arguments as expression, and the other arguments as script. This design makes it possible to create new control structures, like sqlite's eval, which takes the SQL statment and a script that it should evaluate for each result.

splitting a formula and again regenerating and reevaluateing formula

I m splitting a formula string with "*/+-()" as my pattern (for eg. a*b+c is string) and I m getting a list in the output as (a b c) where a,b,c are variables and contain some values like 5,10,15.
What I need is: I should be able to directly substitute values in the variables and evaluate the expression.
The formula is taken from the user and changes time to time. so if the user enters (a/b), something should automatically replace it with real values (5/10) and then return the result 0.5.
The formula is formed from limited number of variables (for eg. a,b,c) and it can use +,-,*,/,(,) as operators.
The problem is that after splitting the variables, i m not able to replace them with their values or evaluate the equation. Please help me to do this task in as short expression as possible. thanks in advance.
It is not at all that complicated:
First, replace all variables with with a Tcl variable (prepped a $).
You have to be careful not replace sin(a) with $sin($a) or similar.
regsub -all -inline {[a-z]+(?![a-z\(])} $input
Example:
set input {a*b+c+sin(d)}
regsub -all -inline {[a-z]+(?![a-z\(])} $input
would yield $a+$b*$c+sin($c), which can be passed to expr.
If you need the variable names, just use regexp with this expression.
If you know the names of the variables and none of them are prefixes of anything else you use, you can easily transform the expression like this:
set a 1; set b 2; set c 3
set e "a*b+c"
set value [expr [string map {a $a b $b c $c} $e]]
puts "$e = $value"
Note: no braces around the expression on the third line. This is when you want to avoid safety like that because you are doing runtime generation of the expression.
That mapping can be generated automatically:
set a 1; set b 2; set c 3
set e "a*b+c"
set vars {a b c}
set value [expr [string map [regsub -all {\w+} "& $&"] $e]]
puts "$e = $value"
However, if you've got prefixes and other things like that, you need a more complex transform:
# Omitting the variable setup and print at the end...
proc replIfRight {vars word} {
if {$word in $vars} {return \$$word} else {return $word}
}
set value [expr [subst [regsub -all {\w+} [string map {[ \[ $ \$ \\ \\\\} $e] {[replIfRight $vars &]}]]]
You're absolutely right to not expect to come up with such a horrible thing yourself!

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?
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.
You may also try to do it yourself. This is a draft proc which you could use as a starting point. It is not production ready, and you must be carefull because substitutions after the first one work on already substituted string.
These are the parameters:
options is a list of options that will be passed to every call to regsub
resubList is a list of key/value pairs, where the key is a regular expression and the value is a substitution
string is the string you want to substitute
This is the procedure, and it simply calls regsub multiple times, once for every element in resubList and, at the end, it returns the final string.
proc multiregsub {options resubList string} {
foreach {re sub} $resubList {
set string [regsub {*}$options -- $re $string $sub]
}
return $string
}

Performing crontab-like numeric matching in a shell script

What would be the most straightforward way to emulate the same numeric matching that is used for the expressions in the first five fields of a crontab line?
For example, given inputs of a pattern "1,5-7,16,*/3" (silly example, I know) and a value "6", the output would be a boolean true.
If there isn't a dead simple solution, it'd be realistic in my situation to provide a third input which would specify the maximum value that an asterisk would need to match, so that asterisks (along with the hyphenated ranges) could be translated to a list of values and the input value could be matched against that list. (The list of the example pattern above would be "1,3,5,6,7,9,12,15,16,18", given a maximum value of "18".)
Thanks!
I'm mostly a ksh person, but my experience with bash says this should work (given your example), or at least point you towards what needs to be done.
hrVal=6
case ${hrVal} in
1|[5-7]|16 ) print -- "true" ;;
* ) print -- "false" ;;
esac
Edit 2018-08-20 For bash, you'll need to change print -- to either echo .... or printf "%s\n".
In reality, I would remove the print -- "" stuff, and just call exit 0 or exit 1, which will then exit with the appropriate return code, that can then be tested by the calling process.
to include the rest of your example, I had to do
hrVal=6
eval "
case ${hrVal} in
1|[5-7]|16|$(( ${hrVal} / 3 )) ) print -- "true" ;;
* ) print -- "false" ;;
esac
"
So, this could be exciting!
Parse each of 5 time bands as above
apply sed like commands to convert he entries like 1,5-7,16 into 1|[5-7]|16
trap and convert your math expressions into evaluatable expressions
(oh, you can probably get the result before the case statement and
just merge the value into the ....) save all derived values as
variables,
use those variables as case targets, possible wrapping the whole thing and escaping chars as needed with an eval.
evaluate the combined truth of all 5 columns return values (any false == false)
(maybe it is (( ${hrVal} / 3 )) in bash )
IHTH