Do define/paramter(same as in verilog language) exist in TCL script? - tcl

Is there any command in TCL that is same as #define or parameter in C?
I want to reduce my code in script with define as below:
lappend links [lindex $all_list $i] $j $k 0
-> this command are used many times in my script.
How do I define it define lap_lk lappend links [lindex $all_list $i] $j $k 0 as verilog and using it in TCL script with short command lap_lk?
Thanks you very much :) .

Tcl isn't exactly the same as in C (or other languages that use that preprocessor). However, you can create procedures that have the sort of effects that you want. The type of procedure you want to make depends on whether you are passing in any arguments.
Simple, Parameterless
In the simplest case, without arguments, you can just use uplevel inside the procedure to run some code in the caller's context:
proc lap_lk {} {
uplevel 1 {
lappend links [lindex $all_list $i] $j $k 0
}
}
The code inside the uplevel (the 1 is optional, but I recommend it for clarity) is simply run as if it was run instead of the call to lap_lk; it uses the variables that are visible to the caller.
With Parameters
However, if you are taking arguments then things get more complicated. Let's assume that you are taking in the index into $all_list, $i, and the parts $j and $k, and let's assume that they're not too horrible…
# Using A B C to make it clear that these are different things
proc lap_lk {A B C} {
uplevel 1 [subst {
lappend links \[lindex \$all_list $A] [list $B] [list $C] 0
}]
}
The key here is that I'm using subst to inject things into the script (I could have also used double-quotes around the script instead) and I'm using list to add exactly the quoting to $B and $C to make them substitution-safe; I really ought to do so around $A, except that indices usually are substitution-safe anyway. You'd then call the code like this:
lap_lk $i $j $k
Toward the Tao of Tcl
However, in more complicated case (such as where a non-trivial loop is required) then you will use uplevel and upvar to do something more subtle. That's where you're going almost completely beyond what a C preprocessor can do elegantly. (Using a level-target other than 1 is where you go completely beyond the capabilities of the preprocessor.) In this case, using upvar lets us completely avoid using uplevel.
proc lap_lk {targetList sourceList A B C} {
upvar 1 $targetList tgt $sourceList src
lappend tgt [lindex $src $A] $B $C 0
}
# Calling pattern
lap_lk links all_list $i $j $k
It's recommended if you work this way to pass in the names of all (relevant) local variables as arguments rather than hard-coding the context. It's usually considered good practice to make procedures as independent of their calling context as possible. It's not always possible, of course.
Changing the above code to work with passing no explicit arguments (by hard-coding the names of the variables) is left as an exercise.

Related

How to use loop index variable inside command that will be invoked later

Let's consider the following code:
package require Tk
proc test {} {
foreach n {
1 2 3 4 5 6 7 8 9
} {
pack [button ._$n -text $n -command {puts $n}]
}
}
test
When one of the buttons invoked, "n" is unknown.
I found a away to address this by changing {puts $n} to "puts $n", but not sure this is a correct approach.
The callback for the button command is executed in the global scope, and there's no n variable there.
If you add global n command into that proc, then the error message won't appear, but each button will print the same value of n.
As you intent is to associate the value of n for each button, you need to pick a different quoting mechanism: braces prevent variable expansion. See https://www.tcl-lang.org/man/tcl8.6/TclCmd/Tcl.htm -- Shawn's comment gave you the answer.
You have to bind the current value of $n to the callback at the time you set it; your code as it stands uses whatever happens to be in the global n variable at the time that the callback is invoked (i.e., it is either the same for all the buttons or an error).
The list command is designed to be perfect for doing that binding; it generates a list, yes, but it also guarantees to generate a substitution-free command that has the words that are its arguments. That is, the script/command call:
eval [list $a $b $c]
is guaranteed to be the same as:
$a $b $c
for any values at all. This is an exceptionally useful property for almost any kind of code generation since it lets you make a script — that can be a call to a procedure for anything complicated — and pass any values over, entirely safely. In your case, this means that you should change change:
pack [button ._$n -text $n -command {puts $n}]
to:
pack [button ._$n -text $n -command [list puts $n]]
Thanks for your answers!
I ended up with the following possible ways:
"puts $n" - good for simple cases, but a bit non-intuitive
[list puts $n] - good, especially for lists
[subst {puts $n}] - good, straightforward substitution

TCL use a variable to generate a varaible and use a variable for file open/close

As an easy example I just want to loop thorugh opening/closing files and use a variable to create another variable. In PERL this is pretty easy but I cnat figure it out in TCL
set gsrs ""
lappend gsrs "sir"
lappend gsrs "dir"
foreach gsr $gsrs {
set file "sdrv/icc/instance_toggle_overwrite.$gsr.txt"
puts "*** I : Generating $file"
set tempGSR gsr
puts "$$tempGSR" # would like output to be value of $gsr
set $gsr [open $file "w"] # normally you would not use a variable here for filename setting
close $$gsr
}
Double-dereferencing is usually not recommended, as it leads to complex code that is quite hard to maintain. However, if you insist on doing it then use set with one argument to do it:
puts [set $tempGSR]
Usually, thinking about using this sort of thing is a sign that either upvar (possibly upvar 0) or an array should be used instead.

TCL commenting out items in a list

Is there a simple way of commenting out items in a list?
set ll [list \
tom \
# dick \
# harry \
martha]
puts [llength $ll]
Quite annoyingly notepad++ and vim highlight the lines with #, and the the line containing martha, fooling me into thinking that they were commented out.
I thought the length would be 2 but it was 6 - it counted the # as separate list items. I have a fairly long list and sometimes I would like to run the script without those items. I would like the keep the original items in the list so that the next person modifying it knows what is available and can comment/uncomment the items accordingly.
The only alternative I could think of was if the item in the list is a #, then skip the next item. Is there a simpler way of doing this?
To allow configurable content in a data structure, so that you can have every item available but only actually use those that you want to use in the current configuration, it is better to use one of several strategies to select the items to be used, instead of trying to make the interpreter do the selection for you.
One possible way to do this:
lmap item [concat {*}{
tom
dick
#harry
#martha
}] {if {[string match #* $item]} {
continue
} else {
set item
}}
In Tcl, only commands can be commented out, and there are pitfalls involved even when you try that.
The # syntactic marker is only interpreted as the start of a comment if the name of a command was expected in that place. Everywhere else, it's just a normal character (even if syntax highlighting often mistakes it for a comment).
# ceci n'est pas une 'comment'
upvar #0 foo bar
To temporarily remove items from a list, for e.g. debugging purposes, it is better to just redo the definition:
set ll [list \
tom \
dick \
harry \
martha]
set ll [list \
tom \
martha]
The last definition will be used. This can become confusing if the change isn't limited to one editing session. To avoid this, saving the original code and completely rewriting the code in use can be a good choice.
See the Scripted List command on the Tcl wiki: http://wiki.tcl.tk/scripted+list
You can't comment out elements like that. Tcl's built-in comment processing only finds comments at points where command names can start, and that's not in the middle of arguments to the list command. Or any other command (though some commands take scripts, which can contain comments that are parsed as such when the script is parsed).
If you were building the list like this:
set myList {
a b c
# commented out
d e f
}
then it would be possible to make things work by post-processing that string before treating it as a list. I do this quite a bit in my longer scripts.
set myList [regsub -all -line {^\s*#.*$} $myList ""]
It's not a perfect solution as it possible to defeat it by being tricky, but it works fine for me.
However, when dealing with list construction like this:
set myList [list a b c \
# commented out \
d e f]
That's much more complicated! The problem is that the newline has already gone by the time the list is written into myList; the only workable fix I can think of is to make source preprocess the whole script! A way to do that is below. (This is not perfect; some introspection techniques can detect what is going on.)
proc source args {
# Argument parsing; the full works
set enc [encoding system]
if {[llength $args] > 1 && [lindex $args 0] eq "-encoding"} {
set enc [lindex $args 1]
set args [lrange $args 2 end]
}
if {[llength $args] != 1} {
return -code error \
"wrong # args: should be \"source ?-encoding name? fileName\""
}
set fileName [lindex $args 0]
# Read in the script
set f [open $fileName]
fconfigure $f -encoding $enc -translation auto -eofchar \x1a
set script [read $f]
close $f
# Pre-process the script; note that we're more careful with backslashes here
set script [regsub -all -line {^\s*#.*([\\]?)$} $script {\1}]
# Evaluate the script in the caller while setting [info script]
info script $fileName
uplevel 1 $script
}
Again, this isn't perfect, but it's likely to work fine for your code as long as you define this procedure replacement for source before loading in your real code. But I just use the first technique — post-processing the lists I've put comments in after construction — instead, and I don't use it for short lists anyway (since those can always have a comment before instead).

how lappend $varname1 $varname2 will be executed

I have searched for lappend $var1 $var2, but don't find any exact answer, how it will be executed.
% set a {a b c}
a b c
% set b {d e}
d e
% puts [lappend $c $b]
can't read "c": no such variable >>> here it throws error like variable not exist
% puts [lappend $a $b]
{d e} >>> here it doesn't throw any error, can someone explain it and how to print the value of $a, if $a is a new variable
% puts $$A
can't read "A": no such variable
% puts $$a
$a b c
% puts ${$a}
can't read "$a": no such variable
Tcl's got a two level syntax that it applies rigorously to everything. The first level is the Tcl generic syntax, which takes:
lappend $var1 $var2
and parses it out to three words: lappend, a word obtained by reading the variable var1, and a word obtained by reading the variable var2.
Then Tcl dispatches to the command named by the first word (lappend, a Tcl built-in) which applies command syntax handling. In the case of lappend, it's pretty simple: the first argument names a variable and the second and subsequent arguments are words to append to the list in the named variable.
In your case, the first argument that names a variable is obtained by reading another variable (var1) and the value to append to the list is coming from a variable (var2); a name like a b c d e is a legal variable name in Tcl, but it's really awkward to use. And the chance is very high that you don't want to write that: putting variable names in a variable is usually an indicator of confusing code. You can do it, but you hardly ever want to do it (except when you're using the variable name with upvar). You probably really meant to write:
lappend var1 $var2
Tcl is very exact about the distinction between variable names and variable contents. The $ is not decorative! It's there to say “read this variable, right now”, and $var1 is virtually equivalent to [set var1] in semantic terms. (The $ shorthand was later, a Tcl 2.0 feature from way back in the day!)
Tcl also doesn't allow double-dereferencing with $$vrbl. In the rare cases you need it, you do [set $vrbl]. And if you do that, you probably should immediately see if you can use an array instead as that's typically a better choice…
lappend's first parameter is a variable name, not a value. Therefore, in general, it should be:
lappend var1 $var2
where both var1 and var2 are list variables. See the Tcl lappend man page for more details.

Getting unevaluated tcl arguments

What I want to do is parse an argument to a tcl proc as a string without any evaluation.
For example if I had a trivial proc that just prints out it's arguments:
proc test { args } {
puts "the args are $args"
}
What I'd like to do is call it with:
test [list [expr 1+1] [expr 2+2]]
And NOT have tcl evaluate the [list [expr 1+1] [expr 2+2]]. Or even if it evaluated
it I'd still like to have the original command line. Thus with the trivial "test"
proc above I'd like to be able to return:
the args are [list [expr 1+1] [expr 2+2]]
Is this possible in tcl 8.4?
You cannot do this with Tcl 8.4 (and before); the language design makes this impossible. The fix is to pass in arguments unevaluated (and enclosed in braces). You can then print them however you like. To get their evaluated form, you need to do this inside your procedure:
set evaluated_x [uplevel 1 [list subst $unevaluated_x]]
That's more than a bit messy!
If you were using Tcl 8.5, you'd have another alternative:
set calling_code [dict get [info frame -1] cmd]
The info frame -1 gets a dictionary holding a description of the current command in the context that called the current procedure, and its cmd key is the actual command string prior to substitution rules being applied. That should be about what you want (though be aware that it includes the command name itself).
This is not available for 8.4, nor will it ever be backported. You might want to upgrade!
When passing the arguments into test, enclose them in braces, e.g.:
test {[list [expr 1+1] [expr 2+2]]}