Tcl pass variable as parameters to function - variables

Using tcl, i want to pass variable parameters to function.
i tried this code
proc launch_proc { msg proc_name {params {}} } {
puts "params launch_proc is $params \n"
}
proc test { param } {
puts "param test is $param \n"
launch_proc "1.5.2 test param" test_standard {{*}$param param1 param2 param3"
}
}
test value
--> params launch_proc is {*}$param param1 param2 param3"
$param is not evaluated (i use tcl 8.5)

Tcl has support for this using the keyword args as the last parameter in the argument list.
Here is an example directly from the wiki:
proc demo {first {second "none"} args} {
puts "first = $first"
puts "second = $second"
puts "args = $args"
}
demo one
demo one two
demo one two three four five
results in
first = one
second = none
args =
first = one
second = two
args =
first = one
second = two
args = three four five
You can use the expand syntax for this as well which makes the following two calls to demo equivalent.
demo one two three four five
set params {three four five}
demo one two {*}$params

You're passing a list and need to instead send each item as a parameter to the proc.
proc test {p1 p2 p3} {
puts "$p1 - $p2 - $p3"
}
set value {one two three}
# This should work in tcl 8.5+
test {*}$value
set value {four five six}
# For tcl < 8.5
foreach {p1 p2 p3} $value {
test $p1 $p2 $p3
break
}
# or
set value {seven eight nine}
test [lindex $value 0] [lindex $value 1] [lindex $value 2]
Output:
$ tclsh test.tcl
one - two - three
four - five - six
seven - eight - nine

You need upvar, and use list when you construct the params list for launch_proc
proc test {varname} {
upvar 1 $varname value
puts "param test is $varname => $value"
launch_proc "1.5.2 test param" test_standard [list {*}$value par1 par2 par3]
}
proc launch_proc {msg proc_name {params {}}} {
puts "params launch_proc: [join $params ,]"
}
set value {"a b c" "d e f" "g h i"}
test value
param test is value => "a b c" "d e f" "g h i"
params launch_proc: a b c,d e f,g h i,par1,par2,par3

Related

Constraining multis and its use for selecting them

Constraints are apparently not used to select one in a multi
multi sub cuenta( Str $str ) { say $str };
multi sub cuenta( $file where .IO.e ) { say $file.IO.slurp };
cuenta( $*PROGRAM-NAME ); # Outputs the file name
That means it's using the first multi, not the second. However, this works as intended:
subset real-files of Str where .IO.e;
multi sub cuenta( Str $str ) { say $str };
multi sub cuenta( real-files $file ) { say $file.IO.slurp };
cuenta( $*PROGRAM-NAME );
printing the content of the program itself. This probably says something about type checking and multi scheduling, but I'm not sure if it's by design or it's just a quirk. Any idea?
multi sub cuenta( Str $str ) { say $str };
multi sub cuenta( Str $file where .IO.e ) { say $file.IO.slurp };
# ^^^
cuenta( $*PROGRAM-NAME ); # Outputs the file
subset real-files where .IO.e;
# ^^^^^^
multi sub cuenta( Str $str ) { say $str };
multi sub cuenta( real-files $file ) { say $file.IO.slurp };
cuenta( $*PROGRAM-NAME ); # Outputs the file name
The base type of a parameter is checked first to establish candidates. Only the narrowest matching multis are candidates for dispatch. A where constraint only applies if there are multiple matching candidates with the same base type. If not specified, the base type of a parameter or a subset is Any.
This is by design.

Perl - don't send double sql request

i have a perl script and i don't want to send a double request :
the request is '2018-03-15 12:30:00', 'Metric A', 62 and i want send only one time and not more :
in my mariadb bdd i have double line :
SELECT time, measurement, valueOne FROM `metric_values`;
results :
+---------------------+-----------------+----------+
| time | measurement | valueOne |
+---------------------+-----------------+----------+
| 2018-03-15 12:30:00 | Metric A | 62 |
| 2018-03-15 12:30:00 | Metric A | 62 |
my perl scipt :
use DBI;
open (FILE, 'logfile');
while (<FILE>) {
($word1, $word2, $word3, $word4, $word5, $word6, $word7, $word8, $word9, $word10, $word11, $word12, $word13, $word14) = split(" ");
$word13 =~ s/[^\d.]//g;
if ($word13 > 5) {
if ($word2 eq "Jan") {
$word2 = "01"
}
if ($word2 eq "Feb") {
$word2 = "02"
}
if ($word2 eq "Mar") {
$word2 = "03"
}
if ($word2 eq "Apr") {
$word2 = "04"
}
if ($word2 eq "May") {
$word2 = "05"
}
if ($word2 eq "Jun") {
$word2 = "06"
}
if ($word2 eq "Jul") {
$word2 = "07"
}
if ($word2 eq "Aug") {
$word2 = "08"
}
if ($word2 eq "Sep") {
$word2 = "09"
}
if ($word2 eq "Oct") {
$word2 = "10"
}
if ($word2 eq "Nov") {
$word2 = "11"
}
if ($word2 eq "Dec") {
$word2 = "12"
}
print "'$word5-$word2-$word3 $word4', $word11, $word13 \n";
}
# Connect to the database.
my $dbh = DBI->connect("DBI:mysql:database=db;host=ip",
"titi", 'mp!',
{'RaiseError' => 1}) ;
my $sth = $dbh->prepare(
"INSERT `metric_values` (time, measurement, valueOne) VALUES('$word5-$word2-$word3 $word4', $word11, $word13);")#result is ('2018-03-15 12:30:00', 'Metric A', 62)
or die "prepare statement failed: $dbh->errstr()";
$sth->execute() or die "execution failed: $dbh->errstr()";
print $sth->rows . " rows found.\n";
$sth->finish;
my log file:
Wed Oct 17 04:57:08 2018 : Resource = 'toto' cstep= 'titi' time =23.634s
Wed Oct 17 04:57:50 2018 : Resource = 'toto' cstep= 'titi' time =22.355s
thanks for your response
In a comment, you say this:
i execute this script every 5 minute and that create many same line in the table, i don't want same line in my table
I think this is what is happening.
Every five minutes you run your program. Each time you run the program you use exactly the same log file as input. So the same records get processed every time and new copies of the data are inserted on each run.
There's nothing wrong with your existing code. It's doing exactly what you've asked it to do. It's just not clever enough. You need to make it cleverer. You have a few options.
Remove from the log file the records that have been processed. That way you only insert each record once.
Add a flag to each record in your log file which indicates that it has been added to the database. You can then check that flag when processing the file and only insert records that don't have the flag.
Add an index to your table to ensure that it can only contain one copy of each record. You'll then need to change your code so it ignores any duplicate data errors that you get back from the database.
Use REPLACE instead of INSERT and ensure you have the correct primary key on your table to ensure that duplicate records aren't inserted.
Without knowing a log more about your particular application, it's hard to know which of these options is the best approach for you. I suspect you'll find the REPLACE option the easiest to implement.
Update: I hope you'll find some general comments on your code to be useful.
Your code to open the file works, of course, but it is some distance from current best practice. I recommend a) using a lexical filehandle, b) using the three-arg version of open() and c) checking the return value from the call.
open my $fh, '<', 'logfile'
or die "Could not open 'logfile': $!\n";
Using variables called $word1, $word2, etc is a terrible idea. A better idea would be to use an array:
my #words = split ' ',
If you really want individual variables, then please give them better names:
my ($day, $mon, $date, $time, $year, ... ) = split(' ');
Personally, I'd turn each record into a hash.
my #cols = qw[day mon date time year ... ];
# and then, in your loop
my %record;
#record{#cols} = split ' ';
Converting the month to a number the way you do it is clunky. Consider setting up a conversion hash.
my %months = (
Jan => 1,
Feb => 2,
...
);
Then your code becomes (assuming $mon instead of $word2):
$mon = sprintf '%02d', $months{$mon}
or die "$mon is not a valid month\n";
But, actually, you should use something like Time::Piece to deal with dates and times.
my $timestamp = "$day $mon $date $time $year";
my $tp = Time::Piece->strptime($timestamp, '%a %b %d %H:%M:%S $Y');
say $tp->ymd, ' ', $tp->hms;

Why does this documentation example fail? Is my workaround an acceptable equivalent?

While exploring the documented example raised in this perl6 question that was asked here recently, I found that the final implementation option - (my interpretation of the example is that it provides three different ways to do something) - doesn't work. Running this;
class HTTP::Header does Associative {
has %!fields handles <iterator list kv keys values>;
sub normalize-key ($key) { $key.subst(/\w+/, *.tc, :g) }
method EXISTS-KEY ($key) { %!fields{normalize-key $key}:exists }
method DELETE-KEY ($key) { %!fields{normalize-key $key}:delete }
method push (*#_) { %!fields.push: #_ }
multi method AT-KEY (::?CLASS:D: $key) is rw {
my $element := %!fields{normalize-key $key};
Proxy.new(
FETCH => method () { $element },
STORE => method ($value) {
$element = do given $value».split(/',' \s+/).flat {
when 1 { .[0] } # a single value is stored as a string
default { .Array } # multiple values are stored as an array
}
}
);
}
}
my $header = HTTP::Header.new;
say $header.WHAT; #-> (Header)
$header<Accept> = "text/plain";
$header{'Accept-' X~ <Charset Encoding Language>} = <utf-8 gzip en>;
$header.push('Accept-Language' => "fr"); # like .push on a Hash
say $header<Accept-Language>.perl; #-> $["en", "fr"]
... produces the expected output. Note that the third last line with the X meta-operator assigns a literal list (built with angle brackets) to a hash slice (given a flexible definition of "hash"). My understanding is this results in three seperate calls to method AT-KEY each with a single string argument (apart from self) and therefore does not exersise the default clause of the given statement. Is that correct?
When I invent a use case that excersises that part of the code, it appears to fail;
... as above ...
$header<Accept> = "text/plain";
$header{'Accept-' X~ <Charset Encoding Language>} = <utf-8 gzip en>;
$header{'Accept-Language'} = "en, fr, cz";
say $header<Accept-Language>.perl; #-> ["en", "fr", "cz"] ??
# outputs
(Header)
This Seq has already been iterated, and its values consumed
(you might solve this by adding .cache on usages of the Seq, or
by assigning the Seq into an array)
in block at ./hhorig.pl line 20
in method <anon> at ./hhorig.pl line 18
in block <unit> at ./hhorig.pl line 32
The error message provides an awesome explanation - the topic is a sequence produced by the split and is now spent and hence can't be referenced in the when and/or default clauses.
Have I correctly "lifted" and implemented the example? Is my invented use case of several language codes in the one string wrong or is the example code wrong/out-of-date? I say out-of-date as my recollection is that Seq's came along pretty late in the perl6 development process - so perhaps, this code used to work but doesn't now. Can anyone clarify/confirm?
Finally, taking the error message into account, the following code appears to solve the problem;
... as above ...
STORE => method ($value) {
my #values = $value».split(/',' \s+/) ;
$element = do given #values.flat {
when 1 { $value } # a single value is stored as a string
default { #values } # multiple values are stored as an array
}
}
... but is it an exact equivalent?
That code works now (Rakudo 2018.04) and prints
$["en", "fr", "cz"]
as intended. It was probably a bug which was eventually solved.

How to check a variable is it the Database and print it out?

I got a list of variables to loop through the database. How can I detect the variable is not in the database? What query should I use? How to print out error message once detected the variable is not in database.
My Code:
$variable = $sql->{'variable'};
foreach my $sql (#Records){
**Below statement will select existed variable, what should I change to make it select not existed variable**
$sqlMySQL = "Select LOT from table where LOT like '%$variable%'";
}
**If not exist**{
print("Not exist")
}
Expected result:
While the $variable loop through the database, if the $variable not exist in the database then print out the $variable or not exist.
Thanks for viewing, comments and answers.
I would go about it similar to the below.
A list of variables - Place those variables in an array (aka a list)
What query should I use - One that will only select exactly what you need and store it in the best dataset for traversal (selectall_hashref)
While the $variable loop through the database - Would require a DBI call for each $variable, so instead loop through your array to check for existence in the hash.
EXAMPLE
#!/usr/bin/perl
use strict;
use warnings;
use DBI;
my $dbh =
DBI->connect( "dbi:SQLite:dbname=test.db", "USERNAME", "PASSWORD",
{ RaiseError => 1 },
) or die $DBI::errstr;
my #vars = ( 25, 30, 40 );
my $hash_ref =
$dbh->selectall_hashref( q / SELECT LOT FROM table /, q / LOT / );
$dbh->disconnect();
foreach (#vars) {
if ( exists $hash_ref->{$_} ) {
print $_ . "\n";
}
else {
print "Does not exist\n";
}
}
Something similar to that will pull all the LOT column values for your table into a hash key value pair that you can then compare against your array in a foreach loop.

Query works in Sqlite with strings. When vars passed to query in Tcl it breaks

I have a Sqlite notes database with a "Toxi" schema (Notes table, Tags table, Note-has-Tag table called "fkeys"). There is another thread that goes into this, and the querying of it, it great detail. The interface or "front end" is a Tcl script run from Tclsh. The Tcl version works just fine if I just have 1 arg and no INTERSECT. In SqliteManager INTERSECT works (but the 2 args are replaced with literal strings). Why does it break? First the error message:
% can't read "rowid": no such variable
then the code:
proc gn {args} {
package require sqlite3
sqlite3 db jaysnotes.sqlite
set tagsofar [db eval {select tag_text from tag}]
puts "Tags so far: $tagsofar"
if {$args eq ""} {
puts "Enter 1 or more tags separated by spaces"
gets stdin taglist
} else {
set taglist $args}
set taglist [split $taglist " "]
# note (note_txt, timestamp)
# tag (tag_text)
# fkeys (note_id ,tag_id)
set srchtxt0 [lindex $taglist 0]
if {[llength $taglist] > 1} {
set srchtxt1 [lindex $taglist 1]
} else {set srchtxt1 $srchtxt0}
db eval {
SELECT DISTINCT n.rowid, n.note_txt, n.timestamp
FROM note n
JOIN fkeys f
ON n.rowid = f.note_id
JOIN tag t
ON t.rowid = f.tag_id
WHERE t.tag_text = $srchtxt0
INTERSECT
SELECT DISTINCT n.rowid, n.note_txt, n.timestamp
FROM note n
JOIN fkeys f
ON n.rowid = f.note_id
JOIN tag t
ON t.rowid = f.tag_id
WHERE t.tag_text = $srchtxt1
ORDER BY timestamp;} {puts "NOTE $rowid: $note_txt"
puts "DATE: $timestamp\n"}
}
Nevermind, as soon as I changed the 2 lines to have the "as ...." like so:
SELECT DISTINCT n.rowid as rowid, n.note_txt as note_txt, n.timestamp as timestamp
Everything worked. Sorry to waste your time. Hope someone benefits.