Im trying to modify a variable using upvar (in an upward stack), but the value of the variable is passed to the procedure and not the variable name.
I cannot change what is passed, since it is already implemented widely on the program.
Is there a way to modify the file name in some way ?
proc check_file_exists {name} {
upvar $name newName
check_exists $name #Do all checks that file is there
set newName $name_1
}
check_file_exists $name
puts $name
this code will print the old name of the file and not the new one.
What I think you should do is bite the bullet and change the calls. It's a fairly simple search-and-replace, after all. The code will be more sane than if you use any of the other solutions.
check_file_exists name
Or, you could add another parameter to the parameter list and use that to pass the name, making the first argument a dummy argument.
check_file_exists $name name
Or, if you're not using the return value, you could return the new value and assign it back:
set name [check_file_exists $name]
Or, you could assign the new value to a global variable (e.g. theValue) inside the procedure, and assign that back:
check_file_exists $name
# don't need this if you're in global scope
global theValue
set name $theValue
Or, you could assign the name to a global variable (e.g. theName) and access that inside the procedure: the procedure will be able to update name directly.
# don't need this if you're in global scope
global theName
set theName name
check_file_exists $name
(There are some variations on this f.i. using upvar.)
None of the alternatives are pretty, and all of them still require you to make a change at the call (except the last, if you only ever use one variable for this value). If you're adamant about not doing that, there's always Donal's info frame solution, which only requires the procedure itself to be changed.
Let me know if you want help with the procedure code for any of these alternatives.
This is quite difficult; it's really not the way you're supposed to work. But you can do it using info frame -1 (a facility usually used for debugging) to find out exactly how the current procedure was called. However, you need to be careful as the caller might be using the result of a command: this is an unsafe hack.
proc check_file_exists {name} {
set caller [dict get [info frame -1] cmd]
if {[regexp {^check_file_exists +\$(\w+)} $caller -> varName]} {
# OK, we were called with a simple variable name
puts "Called with variable $varName"
} else {
# Complicated case! Help...
return -code error "must be called with a simple variable's contents"
}
upvar 1 $varName newName
check_exists $name
set newName $name_1
}
Related
In TCL is it possible to have the default values of parameters be the return value of a function call?
proc GetParameterValue { } {
# calculation for value...
return value
}
proc TestFunction { {paramVal [GetParameterValue]} } {
puts $paramVal
}
TestFunction
This results in printing "[GetParameterValue]". Rather than calling the procedure GetParameterValue. Is this possible to do in TCL or do I need to redesign this bit of code?
The default values of parameters can only be constants that you compute at the time of declaration of the procedure (most commonly, they're literals which means you don't need to use list to do the construction):
proc TestFunction [list [list paramVal [GetParameterValue]]] {
...
}
To compute a default value at procedure call time, you have to move the calculation into the body of the procedure. There's a few ways to do the detection of whether to do the calculation, but they come down to three options: using a marker value, getting a count of words in the call, and taking full control of parsing.
Using a marker value
The trick to this is to find some value that is really unlikely to be passed in. For example, if this is to be a piece of text shown to the user, a value with nothing but an ASCII NUL in it is not going to occur; put that in the default then you can tell whether you've got the default and can substitute with what the complex code provides.
proc TestFunction {{paramVal "\u0000"}} {
if {$paramVal eq "\u0000"} {
set paramVal [GetParameterValue]
}
...
}
Getting a count of words in the call
This relies on the capabilities of the info level introspection command. In particular, info level 0 reports the full list of actual arguments to the current procedure. A bit of counting, and we can can know whether a real value was passed.
proc TestFunction {{paramVal "dummy"}} {
if {[llength [info level 0]] < 2} {
# Note that the command name itself is always present
set paramVal [GetParameterValue]
}
...
}
It's a totally general approach, so there's no worry about the case where someone provides an unexpected edge case, but it's more complicated when you have multiple arguments as you need to work out how many arguments should be present and so on yourself. That is simple in this case, but gets progressively more difficult as you have more arguments.
Taking full control of parsing
Ultimately, you can also decide to make a procedure that takes full control of the parsing of its arguments. You do that by giving it a single argument, args, and then you can use any approach you want to handle the actual argument list. (I tend to not put the formal argument list in parentheses in this case only but that's just my own style.)
proc TestFunction args {
if {[llength $args] == 0} {
set paramVal [GetParameterValue]
} elseif {[llength $args] == 1} {
set paramVal [lindex $args 0]
} else {
# IMPORTANT! Let users discover how to use the command!
return -code error -errorcode {TCL WRONGARGS} \
"wrong # args: should be \"TestFunction ?paramVal?\""
}
...
}
This is currently the only way to do anything truly advanced, e.g., to have optional arguments before mandatory ones. It's also pretty much what you'd have to do in C if you implemented the command there, though adjusted for a different language. The downside is that it is definitely more work than using the built-in basic argument parsing support code provided by the implementation of the proc command.
This is meant as a complement to Donal's thorough answer. In the past, I sometimes resorted to the assistance of [subst] for computed defaults:
proc GetParameterValue {} { return computedDefault }
proc TestFunction {{paramVal [GetParameterValue]}} {
puts [subst -novariables $paramVal]
}
TestFunction; # returns "computedDefault"
TestFunction "providedValue"
TestFunction {$test}
This avoids the need for (implementing) full control over arguments, and is piggybacking onto the built-in argument handler. It also allows for using anonymous procs rather than explicitly named ones for computing the defaults:
proc TestFunction {{paramVal "[apply {{} { return computedValue }}]"}} {
puts [subst -novariables ${paramVal}]
}
TestFunction; # returns "computedDefault"
TestFunction "providedValue"
TestFunction {$test}
It goes without saying that there are also some assumptions behind, which turn into important restrictions depending on one's application case:
You must keep some discipline in using brackets for the defaults in the argument lists, and placing [subst] at the usage sites of the argument variable.
It assumes that you have some control over the arguments, or that you can guarantee that certain special-purpose characters are not valid members of the arguments' value domain.
Watch:
TestFunction {[xxx]}
throws
invalid command name "xxx"
and must be sanitized to
TestFunction {\[xxx\]}
I have tried this:
global svcCallWithOneChar ""
if {[catch {set "svcCallWithOneChar = [mimic.list :prefix \"l\"]"} errmsg]} {
puts $errmsg
} else {
puts "###### Svc Call With prefix set to Single Char \n $svcCallWithOneChar \n ##################"
}
but it produces this error
can't read "svcCallWithOneChar =
The reason is: It goes to else condition and then can't access the result stored in svcCallWithOneChar.
If you are doing object-oriented programming in Tcl, there are several options. In TclOO, you define an instance variable like this:
oo::class create Foo {
variable thevariable
...
}
The variable thevariable is now accessible in all methods in all instances of the Foo class.
As for your example code, I can't really figure out what you are trying to do. A couple of notes, though:
The arguments to the global command are variable names to be linked between the global scope and the current scope. Your invocation, if it is inside a procedure, creates two local variables: svcCallWithOneChar and "" (yes, that is a legal variable name).
The invocation set "svcCallWithOneChar = [mimic.list :prefix \"l\"]" is not an assignment. It's the single-argument form of set, so it tries to access the value of a variable named svcCallWithOneChar = foo (if foo is the return value of the mimic.list :prefix \"l\" invocation). If the latter invocation results in the empty string, the variable name becomes svcCallWithOneChar =, which is exactly what your error message says.
Also, even though mimic.list :prefix \"l\" is invoked within a double-quoted string, the text within brackets is not part of the string (the result of the invocation is embedded verbatim in the string, though). So escaping the double quotes in the second argument to mimic.list means that the command gets the argument "l", not l.
How can I pass strings by reference to the parent scope?
This doesn't work since strings are not acceptable "values".
function Submit([ref]$firstName){
$firstName.value = $txtFirstName.Text
}
$firstName = $null
Submit([ref]$firstName)
$firstName
Error: "Property 'value' cannot be found on this object; make sure it exists and is settable"
Doing this doesn't give an error but it doesn't change the variable either:
$firstName = "nothing"
function Submit([ref]$firstName){
$firstName = $txtFirstName.Text
}
Submit([ref]$firstName)
$firstName
Edit:
Doing the first code block by itself works. However when trying to do it in my script it returns the error again. I fixed it enough for it to assign the variable and do what I want but it still throws up an error and I was wondering how to fix that. I think it's because it doesn't like variable;es changing during a running session. Here is a link to my script
https://github.com/InconspicuousIntern/Form/blob/master/Form.ps1
Your first snippet is conceptually correct and works as intended - by itself it does not produce the "Property 'Value' cannot be found on this object" error.
You're seeing the error only as part of the full script you link to, because of the following line:
$btnSubmit.Add_Click({ Submit })
This line causes your Submit function to be called without arguments, which in turn causes the $firstName parameter value to be $null, which in turn causes the error quoted above when you assign to $firstName.Value.
By contrast, the following invocation of Submit, as in your first snippet, is correct:
Submit ([ref] $firstName) # Note the recommended space after 'Submit' - see below.
[ref] $firstName creates a (transient) reference to the caller's $firstName variable, which inside Submit binds to (local) parameter variable $firstName (the two may, but needn't and perhaps better not have the same name), where $firstName.Value can then be used to modify the caller's $firstName variable.
Syntax note: I've intentionally placed a space between Submit and ([ref] $firstName) to make one thing clearer:
The (...) (parentheses) here do not enclose the entire argument list, as they would in a method call, they enclose the single argument [ref] $firstName - of necessity, because that expression wouldn't be recognized as such otherwise.
Function calls in PowerShell are parsed in so-called argument mode, whose syntax is more like that of invoking console applications: arguments are space-separated, and generally only need quoting if they contain special characters.
For instance, if you also wanted to pass string 'foo', as the 2nd positional parameter, to Submit:
Submit ([ref] $firstName) foo
Note how the two arguments are space-separated and how foo needn't be quoted.
As for an alternative approach:
[ref]'s primary purpose is to enable .NET method calls that have ref / out parameters, and, as shown above, using [ref] is nontrivial.
For calls to PowerShell functions there are generally simpler solutions.
For instance, you can pass a custom object to your function and let the function update its properties with the values you want to return, which naturally allows multiple values to be "returned"; e.g.:
function Submit($outObj){
$outObj.firstName = 'a first name'
}
# Initialize the custom object that will receive values inside
# the Submit function.
$obj = [pscustomobject] #{ firstName = $null }
# Pass the custom object to Submit.
# Since a custom object is a reference type, a *reference* to it
# is bound to the $outObj parameter variable.
Submit $obj
$obj.firstName # -> 'a first name'
Alternatively, you can just let Submit construct the custom object itself, and simply output it:
function Submit {
# Construct and (implicitly) output a custom
# object with all values of interest.
[pscustomobject] #{
firstName = 'a first name'
}
}
$obj = Submit
$obj.firstName # -> 'a first name'
Please try this out and see if you are getting the same results? It is working for me, and I really did not change much.
$txtFirstName = [PSCustomObject]#{
Text = "Something"
}
function Submit([ref]$var){
$var.value = $txtFirstName.Text
}
$firstName = $null
Submit([ref]$firstName)
$firstName
See code below:
namespace eval foo {
variable bar 5
proc getBar {} {
variable bar
}
proc getRetBar {} {
variable bar
return $bar
}
}
puts "\"[ foo::getBar ]\""
puts "\"[ foo::getRetBar ]\""
exit
It outputs:
""
"5"
Why does it not return the variable value, just like the set command would?
Why does it always return an empty string? If I want to access namespace variables through procedure, and not by accessing them directly, it makes the code slightly longer. Not a major issue, but a little annoying
That's the way the command is defined. It makes sense, because if given an odd number of arguments, the last one is a name that will be declared as a namespace variable but need not exist. If it doesn't exist, what value should variable return?
Still, it's no hassle to write a single-command getter procedure (assuming the variable exists):
proc getBar {} {
set [namespace current]::bar
}
Or you could use a TclOO object (note that you need a setter to initialize it):
oo::object create foo
# -> ::foo
oo::objdefine foo variable bar
oo::objdefine foo method setBar v {set bar $v}
oo::objdefine foo method getBar {} {set bar}
foo setBar 5
# -> 5
puts "\"[foo getBar]\""
# => "5"
You can define the object in one call, if you prefer:
oo::objdefine foo {
variable bar
method setBar v {set bar $v}
method getBar {} {set bar}
}
Documentation: namespace, oo::objdefine, oo::object, variable
The proc command returns an empty string by default. When a procedure is invoked, the procedure's return value is the value specified in a return command. If the procedure does not execute an explicit return, then its return value is the value of the last command executed in the procedure's body.
The fun part here is that variable command never returns any value. It just creates/initializes/modifies a namespace variable.
% namespace eval foo {
variable bar 5
proc getBar {} {
variable bar
}
}
If the variable command is executed inside a Tcl procedure, it creates local variables linked to the corresponding namespace variables and therefore these variables are listed by info vars.
% info vars foo::*
::foo::bar
% set foo::bar; # Getting the 'bar' value
5
% variable foo::bar 10; # Note that it does not return any value as such.
%
% set foo::bar
10
%
Since there is no implicit/explicit return value in the procedure getBar, it is returning the empty string which is the default.
Reference : variable, proc
It works that way because that's how it was implemented and documented, which is to say that it is a relatively arbitrary decision. However it is a decision that occurred nearly 20 years ago, and we see no particular reason to revisit it. Who knows what (OK, probably quite ill-advised) code would be broken?
That said, the main use of variable is with a single argument. What would be the result in this case? It's even used when the variable concerned does not exist: there is no sensible result possible at all, and unlike set it is not an error to do that (it allocates some structures in the namespace if necessary, and binds a local variable if in a procedure). The result you ask for would also make little sense when we have four arguments: why would the last thing to be set be privileged this way? The set command rejects this, and so does not need to deal with the philosophical consequences.
It is better to let variable continue to do what it currently does. It might be longer to write an explicit read as well, but it is also considerably clearer as to what the intention of the code is, and that is a thoroughly good thing over the somewhat longer term.
I have a class that has several inner values:
private variable layer_type
private variable color
private variable name
private variable thickness
private variable conductivity
private variable permittivity
private variable loss_tangent
I want to initiate them with values that the user gives to the constructor, but since there 7 of them, to reduce clutter and code size I did it in a loop:
constructor {in_layer_type in_color in_conductivity in_loss_tangent in_name in_permittivity in_thikness f_row frame} {
foreach var [list layer_type color conductivity loss_tangent name permittivity thikness] {
set $var [set in_$var]
}
Is there any difference (performance wise) with this approach, over the writing all the set commands?
set layer_type $in_layer_type
set color $in_color
#...
set thickness $in_thickness
In strict terms of efficiency, writing it all out in full is the most efficient method. I suggest that you don't do that! The problem is that it's difficult to maintain and hard to use (I don't know about you, but I hate calls with lots of mandatory positional arguments in all languages!) Instead, try something like this:
constructor {f_row frame args} {
foreach {var value} $args {
if {[string match -* $var]} {
set [string range $var 1 end] $value
}
}
# ...
}
Yes, you need to write things a bit different to use this:
TheClass theObjToMake $theFRow $theFrame -layer_type 12345 ...
You're also advised to use Itcl's facilities for setting defaults if you're doing this. Pick something sensible and it will all suddenly seem very natural.