This is the new home of the egghelp.org community forum.
All data has been migrated (including user logins/passwords) to a new phpBB version.


For more information, see this announcement post. Click the X in the top right-corner of this box to dismiss this message.

Create variable name on the fly

Help for those learning Tcl or writing their own scripts.
Post Reply
w
willyw
Revered One
Posts: 1209
Joined: Thu Jan 15, 2009 12:55 am

Create variable name on the fly

Post by willyw »

Hello,

Is there a way to create a variable or array name, based on the nick of the user that called the proc that is using the variable or array?

Naming the new var nick-temp would be ok, as I intend to unset it at the end of the proc.

I must be overlooking something....


Thanks
User avatar
arfer
Master
Posts: 436
Joined: Fri Nov 26, 2004 8:45 pm
Location: Manchester, UK

Post by arfer »

You can create a variable with name equal to the value of another variable.

Say for example I set the value of the variable named testnick ($testnick) to arfer, then use it to create a variable named arfer (with value "my nick").

[23:23] <arfer> .tcl set testnick arfer
[23:23] <osmosis> Tcl: arfer
[23:24] <arfer> .tcl set $testnick "my nick"
[23:24] <osmosis> Tcl: my nick
[23:24] <arfer> .tcl set arfer
[23:24] <osmosis> Tcl: my nick

A new variable was created called arfer, with a value "my nick" as evidenced by the final set command above.
I must have had nothing to do
User avatar
speechles
Revered One
Posts: 1398
Joined: Sat Aug 26, 2006 10:19 pm
Location: emerald triangle, california (coastal redwoods)

Post by speechles »

*** n is set to "jim"
<speechles> .set n "jim"
<sp33chy> Ok, set.

*** nick_jim is set to "beam"
<speechles> .set nick_$n "beam"
<sp33chy> Ok, set.

*** show the contents of new variable nick_jim
<speechles> .tcl set nick_jim
<sp33chy> Tcl: beam
setting an array, just use set arrayname($nick) "something"
w
willyw
Revered One
Posts: 1209
Joined: Thu Jan 15, 2009 12:55 am

Post by willyw »

speechles wrote:
*** n is set to "jim"
<speechles> .set n "jim"
<sp33chy> Ok, set.

*** nick_jim is set to "beam"
<speechles> .set nick_$n "beam"
<sp33chy> Ok, set.

*** show the contents of new variable nick_jim
<speechles> .tcl set nick_jim
<sp33chy> Tcl: beam
Thanks for replying.
I'm not there yet.

Based on your above, and that I want to create the variable based on the nick of the user calling it, I tried this:

Code: Select all

bind pub - !test atest_proc

proc atest_proc {nick uhost handle chan text} {

set nick_$nick "beam"
putserv "privmsg $chan :  nick_$nick is set to $nick_$nick "
}
with great suspicion about what might happen with the last line.
But I didn't even get that far.
When I caused the proc to run, I get this in partyline:
Tcl error [atest_proc]: can't read "nick_": no such variable
Next, just to be sure of typos, etc. , I tried this:

Code: Select all

proc atest_proc {nick uhost handle chan text} {

set n "jim"
set nick_$n "beam"
putserv "privmsg $chan :  var named nick_$n is set to $nick_$n "
}
and still get
Tcl error [atest_proc]: can't read "nick_": no such variable
Isn't that strange?... that it works from the command line, but not in a procedure?

What do you think?

setting an array, just use set arrayname($nick) "something"
Wouldn't that just set an element within the array though?
I need the array name to be based on the nick somehow.


Thanks
w
willyw
Revered One
Posts: 1209
Joined: Thu Jan 15, 2009 12:55 am

Post by willyw »

arfer wrote:You can create a variable with name equal to the value of another variable.

Say for example I set the value of the variable named testnick ($testnick) to arfer, then use it to create a variable named arfer (with value "my nick").

[23:23] <arfer> .tcl set testnick arfer
[23:23] <osmosis> Tcl: arfer
[23:24] <arfer> .tcl set $testnick "my nick"
[23:24] <osmosis> Tcl: my nick
[23:24] <arfer> .tcl set arfer
[23:24] <osmosis> Tcl: my nick

A new variable was created called arfer, with a value "my nick" as evidenced by the final set command above.

Not ignoring your post.
Thank you very much for replying too.
I'm still experimenting with this. I think I have a mental block or something as it is easy to lose my place when doing this.
Will let you know if I get it
User avatar
arfer
Master
Posts: 436
Joined: Fri Nov 26, 2004 8:45 pm
Location: Manchester, UK

Post by arfer »

It is the putserv statement that is causing the error, there is nothing wrong with the statement

set nick_$nick "beam"

Apart from it being rather horrendous code

Specifically, it is $nick_$nick that cannot be read. It is not even possible to enclose the name in braces ${nick_$nick} since this prevents the second $ from causing variable substitution.

I'm not sure if there is a solution to your specific method. I would urge you to reconsider what you are doing and why you are doing it. It is rather like having a variable name containing spaces, in that it generally leads to unnecessary difficulties somewhere down the line.

Code: Select all

proc atest_proc {nick uhost handle chan text} {
set $nick "beam"
putserv "privmsg $chan :a variable named $nick has been created with value [subst $$nick]"
}
That code above ought to work but it's very messy. I am beginning to regret answering the original question because this is not something I would do.
I must have had nothing to do
User avatar
arfer
Master
Posts: 436
Joined: Fri Nov 26, 2004 8:45 pm
Location: Manchester, UK

Post by arfer »

Just to continue with the thread, the 'normal' way of creating a variable dynamically that is dependent on the value of another variable is to use arrays

So within the proc above

Code: Select all

set ar($nick) "beam"
Much safer. The array named 'ar' now contains an element who's name is equal to the value of the variable named nick (ie. $nick), and this array element has a value "beam". It can be referrenced within the proc using $ar($nick).

Arrays also lend themselves to some very useful additional Tcl functions.

For example, if I knew an array element name contained the sequence of characters ARF but didn't know the exact nick it represented (element name) was arfer or the case was lower, I could find it using something like

Code: Select all

set name [array names ar -regexp (?i)ARF]
Declare the array as global within the proc if you wish to use it elsewhere.
I must have had nothing to do
n
nml375
Revered One
Posts: 2860
Joined: Fri Aug 04, 2006 2:09 pm

Post by nml375 »

Just to clarify one thing, that has not been covered in the previous posts...

You are currently dealing with variables in local namespaces (within the procs). These do not persist inbetween invocations of the proc. What you need to do, is to store the data in a variable in a global namespace. There are a few ways of doing this, such as using the global command, or full namespace paths.

I'll give you a few examples, which also shows how to handle the issue described by arfer a few posts up...

Code: Select all

bind pub - !test atest_proc

####### Use global command to link the localspace variable to the globalspace one...
proc atest_proc {nick uhost handle chan text} {
 global "nick_$nick"
 set nick_$nick "beam"
 putserv "privmsg $chan :nick_$nick is set to [set nick_$nick]"
}

####### Use full namespace path
proc atest_proc {nick uhost handle chan text} {
 set ::nick_$nick "beam"
 putserv "privmsg $chan :nick_$nick is set to [set ::nick_$nick]"
}

####### Use the upvar command to link the globalspace variable to "thenick"
proc atest_proc {nick uhost handle chan text} {
 upvar #0 nick_$nick thenick
 set thenick "beam"
 putserv "privmsg $chan :nick_$nick is set to $thenick"
}

####### Use full namespace path along with arrays
proc atest_proc {nick uhost handle chan text} {
 set ::nick_($nick) "beam"
 puserv "privmsg $chan :nick_($nick) is set to $nick_($nick)"
}
NML_375
w
willyw
Revered One
Posts: 1209
Joined: Thu Jan 15, 2009 12:55 am

Post by willyw »

arfer wrote:It is the putserv statement that is causing the error, there is nothing wrong with the statement

set nick_$nick "beam"
Ah! so it WAS the last line that I suspected. Didn't realize the proc was running that far.
Apart from it being rather horrendous code
It even looked odd to me. But I was just trying to make minimal changes and trying to watch results, at this point.
Specifically, it is $nick_$nick that cannot be read. It is not even possible to enclose the name in braces ${nick_$nick} since this prevents the second $ from causing variable substitution.

I'm not sure if there is a solution to your specific method. I would urge you to reconsider what you are doing and why you are doing it. It is rather like having a variable name containing spaces, in that it generally leads to unnecessary difficulties somewhere down the line.
I'm getting the feeling that I might be going about trying to accomplish the chore with the wrong ideas right from the start.
I've sent you a pm, that hopefully explains it. I hope you don't mind - just didn't want to drag more ugly code out - yet.

Code: Select all

proc atest_proc {nick uhost handle chan text} {
set $nick "beam"
putserv "privmsg $chan :a variable named $nick has been created with value [subst $$nick]"
}
That code above ought to work but it's very messy. I am beginning to regret answering the original question because this is not something I would do.
This is new to me - the subst command. I've just been to the TCL online manual and looked it up, and I really don't 'get it' yet.
I text searched for $$ to and it was not on the page - I was hoping to find an example explaining the way you used it. It was not there. :(
This has become a side note now - you've made me curious about it. If there is a better way to get my job done, hopefully you'll be able to point me towards it after reading that explanation.

Thanks
w
willyw
Revered One
Posts: 1209
Joined: Thu Jan 15, 2009 12:55 am

Post by willyw »

arfer wrote:Just to continue with the thread, the 'normal' way of creating a variable dynamically that is dependent on the value of another variable is to use arrays

So within the proc above

Code: Select all

set ar($nick) "beam"
Much safer. The array named 'ar' now contains an element who's name is equal to the value of the variable named nick (ie. $nick), and this array element has a value "beam". It can be referrenced within the proc using $ar($nick).

Arrays also lend themselves to some very useful additional Tcl functions.

For example, if I knew an array element name contained the sequence of characters ARF but didn't know the exact nick it represented (element name) was arfer or the case was lower, I could find it using something like

Code: Select all

set name [array names ar -regexp (?i)ARF]
Declare the array as global within the proc if you wish to use it elsewhere.

I'd seen this somewhere, already. It won't work, for what I need... I need it the other way around - array name corresponding to nick. Already have all the elements.
w
willyw
Revered One
Posts: 1209
Joined: Thu Jan 15, 2009 12:55 am

Post by willyw »

nml375 wrote:Just to clarify one thing, that has not been covered in the previous posts...

You are currently dealing with variables in local namespaces (within the procs). These do not persist inbetween invocations of the proc. What you need to do, ....
Why?

I don't need the variable outside the proc. Above, I'd said, "...as I intend to unset it at the end of the proc. " and now on second thought, that might not even be necessary. I hadn't thought of that.

And thank you for taking time to try to help.
n
nml375
Revered One
Posts: 2860
Joined: Fri Aug 04, 2006 2:09 pm

Post by nml375 »

willy,
Sorry, mis-read that line. No need for namespaces then.

The "[set nick_$nick]" approach of reading the variable should help you avoid the error of "can't read nick_: no such variable".
It might be possible to use the upvar trick with 0 level instead of #0, but I have not verified that.
NML_375
User avatar
arfer
Master
Posts: 436
Joined: Fri Nov 26, 2004 8:45 pm
Location: Manchester, UK

Post by arfer »

Subst is a command that is used to force variable, command or backslash substitution where it might otherwise fail to occur.

Take the following example :-

[00:15] <arfer> .tcl set a one
[00:15] <Baal> Tcl: one
[00:16] <arfer> .tcl set $a two
[00:16] <Baal> Tcl: two

[00:17] <arfer> .tcl set c $$a
[00:17] <Baal> Tcl: $one
[00:17] <arfer> .tcl set c [subst $$a]
[00:17] <Baal> Tcl: two

$$a resulted in an unexpected return value. Seemingly the innermost $ did not invoke substitution. This was forced by using subst command, yielding the expected result.
I must have had nothing to do
w
willyw
Revered One
Posts: 1209
Joined: Thu Jan 15, 2009 12:55 am

Post by willyw »

arfer wrote:Subst is a command that is used to force variable, command or backslash substitution where it might otherwise fail to occur.

Take the following example :-

[00:15] <arfer> .tcl set a one
[00:15] <Baal> Tcl: one
[00:16] <arfer> .tcl set $a two
[00:16] <Baal> Tcl: two

[00:17] <arfer> .tcl set c $$a
[00:17] <Baal> Tcl: $one
[00:17] <arfer> .tcl set c [subst $$a]
[00:17] <Baal> Tcl: two

$$a resulted in an unexpected return value. Seemingly the innermost $ did not invoke substitution. This was forced by using subst command, yielding the expected result.
Thanks for the explanation. It still makes me do a double take though. :)

When you get a free moment, please have a look at your pm inbox here, and let me know what you think of what I'm trying to do. Again, I suspect there is a better way, but I need direction to get on the right path.

Thanks
User avatar
arfer
Master
Posts: 436
Joined: Fri Nov 26, 2004 8:45 pm
Location: Manchester, UK

Post by arfer »

In pm, Willyw has asked me for help with code for a playback script. ie. a script that will remember the last few chat lines said on a channel and, if commanded to do so, will play back the lines by notice. In reality, this was the reason for the thread in the first place.

I thought that anybody following the thread may want to play around with and/or improve on the code, so I will paste it here.

Command syntax is !playback (bot flag o required).

Lines said by the bot (perhaps due to output from other scripts) are excluded as are !playback command lines.

The code below is preconfigured to recall the last 5 lines said in the channel #eggTCL.

Rehash/restart will clear the stored memory.

Code: Select all

# *** configuration *** #

set vPlaybackMemory 5
set vPlaybackChannel #eggTCL

# *** code *** #

bind PUB o !playback pPlaybackOutput
bind PUBM - * pPlaybackStore

if {[info exists vPlaybackData]} {unset vPlaybackData}

proc pPlaybackOutput {nick uhost hand channel txt} {
    global vPlaybackData vPlaybackMemory
    for {set x 1} {$x <= $vPlaybackMemory} {incr x} {
        if {[info exists vPlaybackData($x)]} {puthelp "NOTICE $nick :$vPlaybackData($x)"}
    }
    return 0
}

proc pPlaybackPromote {nick channel} {
    if {[isop $nick $channel]} {
      return "<@$nick\>"
    } elseif {[isvoice $nick $channel]} {
      return "<+$nick\>"
    } else {return "<$nick\>"}
}

proc pPlaybackStore {nick uhost handle channel txt} {
    global vPlaybackChannel vPlaybackData vPlaybackMemory
    if {[string equal -nocase $channel $vPlaybackChannel]} {
        if {![string match "!playback*" $txt]} {
            if {![isbotnick $nick]} {
                for {set x 1} {$x < $vPlaybackMemory} {incr x} {
                    if {[info exists vPlaybackData([expr {$x + 1}])]} {set vPlaybackData($x) $vPlaybackData([expr {$x + 1}])}
                }
                set vPlaybackData($vPlaybackMemory) "[pPlaybackPromote $nick $channel] $txt"
            }
        }
    }
    return 0
}
If I were to expand on the script, it would be to include other types of channel activity in the memory. ie. joins/parts/actions/modes etc. Outputting the memory automatically onjoin would probably be mega annoying so I avoided doing that.
I must have had nothing to do
Post Reply