How can I safely deal with optional parameters - tcl

I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.

Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar

I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.

It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.

Related

What is the proper way to create hyphen parameters in TCL that are Boolean or have values? [duplicate]

I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.
Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar
I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.
It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.

how to create tcl proc with hyphen flag arguments

Im searching all over the internet , i guess im searching not the right keywords
i tried most of them :)
i want to create in tcl/bash a proc with hyphen flags to get arguments with flags from the user
ex.
proc_name -color red -somethingselse black
It's very easy to do, actually. This code allows abbreviated option names, flag options (-quxwoo in the example) and the ability to stop reading options either with a -- token or with a non-option argument appearing. In the example, unknown option names raise errors. After passing the option-parsing loop, args contains the remaining command-line arguments (not including the -- token if it was used).
proc foo args {
array set options {-bargle {} -bazout vampires -quxwoo 0}
while {[llength $args]} {
switch -glob -- [lindex $args 0] {
-bar* {set args [lassign $args - options(-bargle)]}
-baz* {set args [lassign $args - options(-bazout)]}
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
-- {set args [lrange $args 1 end] ; break}
-* {error "unknown option [lindex $args 0]"}
default break
}
}
puts "options: [array get options]"
puts "other args: $args"
}
foo -barg 94 -quxwoo -- abc def
# => options: -quxwoo 1 -bazout vampires -bargle 94
# => other args: abc def
This is how it works. First set default values for the options:
array set options {-bargle {} -bazout vampires -quxwoo 0}
Then enter a loop that processes the arguments, if there are any (left).
while {[llength $args]} {
During each iteration, look at the first element in the argument list:
switch -glob -- [lindex $args 0] {
String-match ("glob") matching is used to make it possible to have abbreviated option names.
If a value option is found, use lassign to copy the value to the corresponding member of the options array and to remove the first two elements in the argument list.
-bar* {set args [lassign $args - options(-bargle)]}
If a flag option is found, set the corresponding member of the options array to 1 and remove the first element in the argument list.
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
If the special -- token is found, remove it from the argument list and exit the option-processing loop.
-- {set args [lrange $args 1 end] ; break}
If an option name is found that hasn't already been dealt with, raise an error.
-* {error "unknown option [lindex $args 0]"}
If the first argument doesn't match any of the above, we seem to have run out of option arguments: just exit the loop.
default break
Documentation: array, break, error, lassign, lindex, llength, proc, puts, set, switch, while
With array set, we can assign the parameters and their values into an array.
proc getInfo {args} {
# Assigning key-value pair into array
# If odd number of arguments passed, then it should throw error
if {[catch {array set aInfo $args} msg]} {
return $msg
}
parray aInfo; # Just printing for your info
}
puts [getInfo -name Dinesh -age 25 -id 974155]
will produce the following output
aInfo(-age) = 25
aInfo(-id) = 974155
aInfo(-name) = Dinesh
The usual way to handle this in Tcl is by slurping the values into an array or dictionary and then picking them out of that. It doesn't offer the greatest amount of error checking, but it's so easy to get working.
proc myExample args {
# Set the defaults
array set options {-foo 0 -bar "xyz"}
# Read in the arguments
array set options $args
# Use them
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
Doing error checking takes more effort. Here's a simple version
proc myExample args {
array set options {-foo 0 -bar "xyz"}
if {[llength $args] & 1} {
return -code error "must have even number of arguments in opt/val pairs"
}
foreach {opt val} $args {
if {![info exist options($opt)]} {
return -code error "unknown option \"$opt\""
}
set options($opt) $val
}
# As before...
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
# And here are the errors it spits out...
myExample -spregr sgkjfd
# unknown option "-spregr"
myExample -foo
# must have even number of arguments in opt/val pairs
#flag defaults
set level 1
set inst ""
# Parse Flags
while {[llength $args]} {
set flag [lindex $args 0]
#puts "flag: ($flag)"
switch -glob $flag {
-level {
set level [lindex $args 1]
set args [lrange $args 2 end]
puts "level:($level) args($args)"
} -inst {
set autoname 0
set inst [lindex $args 1]
set args [lrange $args 2 end]
puts "inst:($inst) args($args)"
} -h* {
#help
puts "USAGE:"
exit 1
} -* {
# unknown option
error "unknown option [lindex $args 0]"
} default break
}
}
# remaining arguments
set filename "$args"
puts "filename: $args"

Unable to puts in a tcl script

I am trying to create a trace function in tcl .Function will list down all the called procs/nested calls and there arguments. Below is the script
rename proc _proc
proc proc {nm params body} {
_proc $nm $params $body
trace add execution $nm enter [list track_entry $nm $params]
trace add execution $nm leave [list track_leave $nm]
}
_proc track_entry {nm params real args} {
puts "Enter proc $nm"
foreach formal $params actual [lrange $real 1 end] {
append p " [lindex $formal 0]=$actual,"
}
puts "Parameters:$p and body"
}
_proc track_leave {nm args} {
puts "Exit proc $nm"
}
proc test1 { param1 param2 } {
puts “parameters are $param1 and $param2”
test2 value1
}
proc test2 { value} {
puts “value is $value”
}
I am getting below output
test1 arg1 arg2
Enter proc test1
Parameters: param1=arg1, param2=arg2, and body
Exit proc test1
wrong # args: should be "puts ?-nonewline? ?channelId? string"
Any clue why it is giving error in puts
Provided that what you posted is correct, the problem is you're not using the correct quoting character.
Tcl only understands two kinds of quoting:
quoting with substitutions: ""
quoting without substitutions: {}
The character “ in tcl will just be treated as any other character such as a or 5.
Note that without quoting, tcl treats the word as a string without spaces. For example, the following examples are all valid strings:
this_is_a_valid_string
"this_is_a_valid_string"
{this_is_a_valid_string}
Following this simple rule, the following are also valid strings and are all equivalent:
“hello
"“hello"
{“hello}
So when you ask tcl to execute the following:
puts “parameters are $param1 and $param2”
it treats it as:
puts {“parameters} {are} "$param1" {and} "$param2”"
passing 5 arguments to puts.
Obviously this would trigger an error since puts expects either one or two arguments.

TCL procedure call with optional parameters

There is a TCL script which has multiple procedure definitions with similar name func in different namespaces. Procedures look like this:
proc func {a} {
puts $a
}
All that kind of procedures have only one argument a . All that kind of procedures are called from one single line in whole script:
func $a
I need to create another procedure definition also with similar name func in other namespace. But that procedure will have two parameters. That procedure is also need to be called from the same line that other procedures with same name. Procedure looks like this:
proc func {a b} {
puts $a
puts $b
}
I need now to modify the line that calls all that procedures func $a so, that it can call all procedures with one parameter and new procedure which has two parameters. But procedures definitions with one parameter must not be changed. What line that calls all these procedures func $a should look like?
If you want an optional parameter, and you know what the optional value should be if not supplied, you do this:
proc func {a {b "the default"}} {
puts "a is $a"
puts "b is $b"
}
If you need to compute the default value at runtime, the simplest technique is a magic sentinel value that is very unlikely to occur in real input. Such as two ASCII NUL characters (== Unicode U+000000):
proc func {a {b "\u0000\u0000"}} {
if {$b eq "\u0000\u0000"} {
set b "default:$a"
}
puts "a is $a"
puts "b is $b"
}
Otherwise, you can use the magic args value to get the complete list of arguments and do all the work “by hand”:
proc func {a args} {
if {[llength $args] == 0} {
set b "the default..."
} elseif {[llength $args] == 1} {
set b [lindex $args 0]
} else {
error "bad number of arguments!"
}
puts "a is $a"
puts "b is $b"
}
If you're doing that, the info level introspector can help, but things can get complicated…
To call one of two implementations of a command depending on the number of arguments is rather unusual in Tcl code. You can do it providing neither implementation of the command is in the global namespace and you are not wanting the switching behaviour when calling from the namespaces containing the implementations in question.
What you do is you create a procedure in the global namespace (which every other namespace will look for commands in if not present locally) which then chains explicitly to the desired implementation. The main thing you need to enable this is some way of working out which implementation you want in a particular case (such as looking at the length of the argument list).
For Tcl 8.6, you can use tailcall for the chaining for maximum efficiency:
proc ::func args {
if {[llength $args] == 1} {
tailcall ::impl1::func {*}$args
} else {
tailcall ::impl2::func {*}$args
}
}
In Tcl 8.5 you'd write this instead (which is an optimised case in the interpreter):
proc ::func args {
if {[llength $args] == 1} {
return [uplevel 1 [list ::impl1::func {*}$args]]
} else {
return [uplevel 1 [list ::impl2::func {*}$args]]
}
}
In older Tcl versions, you'd use something like this (which is slower):
proc ::func args {
if {[llength $args] == 1} {
return [uplevel 1 ::impl1::func $args]
} else {
return [uplevel 1 ::impl2::func $args]
}
}
None of this is perfect at handling getting the right sort of error messages when you call with entirely the wrong number of arguments, especially if neither implementation formally has optional arguments. Determining that automatically is probably wholly impractical! You end up having to write extra boilerplate code (which is pretty obvious and works in all versions of Tcl in a straight-forward way):
proc ::func args {
if {[llength $args] == 1} {
tailcall ::impl1::func {*}$args
} elseif {[llength $args] == 2} {
tailcall ::impl2::func {*}$args
} else {
# Using the -errorcode is optional really
return -code error -errorcode {TCL WRONGARGS} \
"wrong # args: should be \"func a ?b?\""
}
}
I found the solution from that answer: https://stackoverflow.com/a/22933188/1601703 . We can get the number of argument that procedure accepts and make coresponding if statments that will use corresponding procedure call:
set num [llength [info args func]]
if {$num == 1} {
func $a
} elseif {$num == 2} {
func $a $b
}

In TCL,can we pass parameters in this way?

I have a question about passing parameters in Tcl regarding to the following code:
set name "Ronaldo"
proc GET_PLAYER_INFO {player_id {player_name "$name"}} {
global name
puts $player_name
}
regarding to the code above, we have a global variable "name", and in the parameter list of proc GET_PLAYER_INFO, the default value of parameter player_name is set to "$name"? if the value of name is "ronaldo", it is already double quotation,do we need to put double quotation in the parameters list like this: player_name "$name"? and before we execute the "global name" command, is the default value of player_name is "Ronaldo"? is so, why we need to have "global name" command in our proc?
That won't work as it stands; the $name won't be evaluated at all so the default will be those literal five characters.
If you're binding the default value at the time you create the procedure, you'd do it like this:
proc GET_PLAYER_INFO [list player_id [list player_name $name]] {
...
}
That is, the arguments to proc are just normal things you can construct with Tcl commands and substitutions. This is one of the great things about Tcl.
However, if you're wanting to evaluate that $name at the time the procedure is called, you've got to do it differently. If you've got some kind of value that will never be used for the player name (e.g., the empty string) then it's pretty easy:
proc GET_PLAYER_INFO {player_id {player_name ""}} {
if {$player_name eq ""} {
set player_name $::name
}
...
}
Note that I've used the fully-qualified variable name there. There are other ways to get that name too (e.g., with global, with upvar, with variable, …)
The place where things get tricky is when you've not got a suitable sentinel value at all. At that point, you have to see how many arguments were actually supplied:
proc GET_PLAYER_INFO {player_id {player_name ""}} {
if {[llength [info level 0]] == 2} {
set player_name $::name
}
...
}
The command info level 0 returns the full list of argument words to the current procedure call. This includes the GET_PLAYER_INFO itself and would be a list of length 2 or 3 in a valid call to the definition above. Once the list is available, checking its length is a trivial exercise in llength and numeric comparison. (Using a sentinel value is easier though, and works in 99.99% of cases.)
The final option is to use the special args formal parameter and do the parsing manually:
proc GET_PLAYER_INFO args {
if {[llength $args] < 1 || [llength $args] > 2} {
return -code error "wrong # args: should be \"GET_PLAYER_INFO player_id ?player_name?\""
}
set player_id [lindex $args 0]
if {[llength $args] > 1} {
set player_name [lindex $args 1]
} else {
set player_name $::name
}
...
}
As you can see, this is rather long-winded...