Discussion:
Proper way to split a string?
(too old to reply)
Luc
2024-12-17 04:37:49 UTC
Permalink
Howdy. I have this pop-up dialog that prompts for two pieces of
information: a name (arbitrary string) and a button that will
invoke a file browser so the user can select a file. Then:

bind $::SRw <Return> {set ::PINPUT "$pname|$pfile"}

then:

vwait ::PINPUT
catch {destroy $::SRw}
return $::PINPUT

It works.

But then I use the ::PINPUT variable, splitting it on the pipe |
symbol to identify the two pieces of information. And then I think,
what if the user uses the pipe symbol in $name?

OK, how about this:

bind $::SRw <Return> {set ::PINPUT "$pname::::::::::::::::$pfile"}

And then I think, what if the user's cat walks on the keyboard and
the user actually enters that kind of string?

OK, how about this:

bind $::SRw <Return> {set ::PINPUT "$pname|=MagicSeparator=|$pfile"}

And then I think, what if the user is possessed by Satan and actually
uses that Magic Separator in $name?

As unlikely as that is, I ask of you: what is the wise, serious,
professional way of handling that situation and making sure that
worse things won't happen at sea?
--
Luc
Rich
2024-12-17 05:05:04 UTC
Permalink
Post by Luc
As unlikely as that is, I ask of you: what is the wise, serious,
professional way of handling that situation and making sure that
worse things won't happen at sea?
If you really want both pieces in the same variable then use a proper
list:

bind $::SRw <Return> {set ::PINPUT [list $pname $pfile]}

Then you can access each piece with lindex 0 or lindex 1 (depending
upon which you want at the time) and Tcl will take care of making sure
the two pieces remain separate pieces.

Otherwise, just store them away as two separate variables:

bind $::SRw <Return> {
set ::PINPUT_name $pname
set ::PINPUT_file $pfile
}

Note, your example will likely not work because when the <Return>
binding runs, $pname and $pfile will not be variables in the global
scope, so adjust accordingly.
Emiliano
2024-12-21 03:41:42 UTC
Permalink
On Tue, 17 Dec 2024 01:37:49 -0300
Luc <***@sep.invalid> wrote:

[snip]
Post by Luc
bind $::SRw <Return> {set ::PINPUT "$pname::::::::::::::::$pfile"}
Incidentally, this wont work.

% set pname foo
foo
% set pfile bar
bar
% set ::PINPUT "$pname::::::::::::::::$pfile"
can't read "pname::::::::::::::::": no such variable

This is because there are two variable expansions in this expression:
one is ${pname::::::::::::::::} which is a variable in the (relative to
current scope) pname namespace with the with the empty string {} as
name (equivalent to [namespace eval pname {set {}}]) and the other is
$pfile. As the [namespace] documentation states, two or more colons are
namespace separators, so any number of colons > 2 are equivalent to "::".

Quoting the manual page:
"Extra colons in any separator part of a qualified name are ignored;
i.e. two or more colons are treated as a namespace separator.
A trailing :: in a qualified variable or command name refers to the
variable or command named {}."

Regards
--
Emiliano
Gerald Lester
2024-12-21 05:13:44 UTC
Permalink
Post by Luc
Howdy. I have this pop-up dialog that prompts for two pieces of
information: a name (arbitrary string) and a button that will
bind $::SRw <Return> {set ::PINPUT "$pname|$pfile"}
vwait ::PINPUT
catch {destroy $::SRw}
return $::PINPUT
It works.
But then I use the ::PINPUT variable, splitting it on the pipe |
symbol to identify the two pieces of information. And then I think,
what if the user uses the pipe symbol in $name?
bind $::SRw <Return> {set ::PINPUT "$pname::::::::::::::::$pfile"}
And then I think, what if the user's cat walks on the keyboard and
the user actually enters that kind of string?
bind $::SRw <Return> {set ::PINPUT "$pname|=MagicSeparator=|$pfile"}
And then I think, what if the user is possessed by Satan and actually
uses that Magic Separator in $name?
As unlikely as that is, I ask of you: what is the wise, serious,
professional way of handling that situation and making sure that
worse things won't happen at sea?
]
Rich suggested making it a list. You can do that or make ::PINPUT an array:

set ::PINPUT(state) waiting
bind $::SRw <Return> {
set ::PINPUT(pname) $pname
set ::PINPUT(pname) $pfile
set ::PINPUT(state) done
}

then:

vwait ::PINPUT(state)
catch {destroy $::SRw}
return [array get ::PINPUT]

Where you go to use it, do:

array set resultArray $returnValue

Or use the dict command to access the pieces.
Luc
2024-12-21 19:54:27 UTC
Permalink
Post by Gerald Lester
set ::PINPUT(state) waiting
bind $::SRw <Return> {
set ::PINPUT(pname) $pname
set ::PINPUT(pname) $pfile
set ::PINPUT(state) done
}
vwait ::PINPUT(state)
catch {destroy $::SRw}
return [array get ::PINPUT]
array set resultArray $returnValue
Or use the dict command to access the pieces.
**************************

It's good to know that I was even wronger than I thought.

I have been using a list as Rich suggested, but the use of an array
is very interesting, rather Tcl-ish.

Thank you all for the education.
--
Luc
Rich
2024-12-21 21:12:01 UTC
Permalink
Post by Luc
Post by Gerald Lester
set ::PINPUT(state) waiting
bind $::SRw <Return> {
set ::PINPUT(pname) $pname
set ::PINPUT(pname) $pfile
set ::PINPUT(state) done
}
vwait ::PINPUT(state)
catch {destroy $::SRw}
return [array get ::PINPUT]
array set resultArray $returnValue
Or use the dict command to access the pieces.
**************************
It's good to know that I was even wronger than I thought.
I have been using a list as Rich suggested, but the use of an array
is very interesting, rather Tcl-ish.
And array has the downside of you cannot directly pass an array into a
proc nor directly return an array from a proc [1].

Either of a list or a dict can be passed in and returned directly with
no further 'effort' needed on your part within your code.

A list gives you the data, but without any attached 'names', so you
have to remember that "index 2 is data value Y" elsewhere.

A dict gives you 'named values' (just like an array) so if you prefer
that method, then you can use a dict to 'return' the values out of a
proc, and they will have names just like with an array.

And, you can convert a dict to an array via "array set array_name
$dict_name". Converting an array to a dict works this way "set
dict_name [dict create {*}[array get array_name]]" or by just
'accessing' the list returned from [array get] with the [dict] command,
which will shimmer it into a dict. The explicit [dict create] method
does make it clear in your code that you are purposefully performing
the transform.


[1] Instead you have to do things like "array get name_of_array" to
return it, or pass in the name of the array and use upvar to access it.
Loading...