TCL commenting out items in a list - tcl

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).

Related

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.

how to get specific parameters in a square bracket and store it in to a specific variable in tcl

set_dont_use [get_lib_cells */*CKGT*0P*] -power
set_dont_use [get_lib_cells */*CKTT*0P*] -setup
The above is a text file.
I Want to store */CKGTOP* and */CKTTOP* in to a variable this is the programme which a person helped me with
set f [open theScript.tcl]
# Even with 10 million lines, modern computers will chew through it rapidly
set lines [split [read $f] "\n"]
close $f
# This RE will match the sample lines you've told us about; it might need tuning
# for other inputs (and knowing what's best is part of the art of RE writing)
set RE {^set_dont_use \[get_lib_cells ([\w*/]+)\] -\w+$}
foreach line $lines {
if {[regexp $RE $line -> term]} {
# At this point, the part you want is assigned to $term
puts "FOUND: $term"
}
}
My question is if more than one cells like for example
set_dont_use [get_lib_cells */*CKGT*0P* */*CKOU*TR* /*....] -power
set_dont_use [get_lib_cells */*CKGT*WP* */*CKOU*LR* /*....] -setup
then the above script isn't helping me to store the these "n" number cells in the variable known as term
Could any of u people help me
Thanking you ahead in time
I would go with
proc get_lib_cells args {
global term
lappend term {*}$args
}
proc unknown args {}
and then just
source theScript.tcl
in a shell that doesn't have the module you are using loaded, and thus doesn't know any of these non-standard commands.
By setting unknown to do nothing, other commands in the script will just be passed over.
Note that redefining unknownimpairs Tcl's ability to automatically load some processes, so don't keep using that interpreter after this.
Documentation:
global,
lappend,
proc,
unknown,
{*} (syntax)
Your coding seems like the Synopsys syntax, meaning - it shouldn't work the way you wrote it, I'd expect curly braces:
set_dont_use [get_lib_cells {*/*CKGT*0P* */*CKOU*TR* /*....}] -power
moreover, the \w doesn't catch the *,/ (see this).
If I were you, I'd go for set RE {^set_dont_use \[get_lib_cells \{?([\S*]+ )+\}?\] -\w+$} and treat the resulting pattern match as a list.
Edit:
see this:
% regexp {^set_dont_use [get_lib_cells {?(\S+) ?}?]} $line -> match
1
% echo $match
*/*CKGT*0P*
If you have more than one item in your line, add another parentheses inside the curly braces:
regexp {^set_dont_use \[get_lib_cells \{?(\S+) ?(\S+)?\}?\]} $l -> m1 m2
ect.
Another Edit
take a look at this, just in case you want multiple matches with the same single pattern, but than, instead of \S+, you should try something that looks like this: [A-Za-z\/\*]

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

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.

non-case-sensitive version of file exists command

Well, not sure what to do in this regard. A little while ago I modified a logging script for an eggdrop bot.. but now an issue unfolds that for some reason, it is logging actions/text in separate files because of an issue of character case. #channel.html exists, as does #Channel.html, though the former is written to because of the current state of the channel name(it can change if all users leave and one rejoins with different case).
I've narrowed this problem down to what I believe is the issue. file exists 'filename_here'. I've looked through tcl's documentation, and I've read through the wiki regarding mixed case file names(it treats them as different files of course), but I have yet to find such an option(or user made proc) that would allow me to disable this behavior.
Is there a way around/to do this?
It really depends on the file system (i.e., the OS) as file exists is just a thin wrapper around the OS's basic file existence test. Classic Unix filesystems are mostly case-sensitive, whereas Windows filesystems are usually case-insensitive. This means that it is usually best to write your code to be careful with handling the case of things; you probably ought to consider using string tolower to get a channel name in an expected case (since I think IRC channel names are case-insensitive).
But if you can't do that, the best you can do is to get the list of filenames that match case-insensitively and check if that's a single value. Alas, this is a messy operation as glob doesn't have a -nocase option (it's rare that people want such a thing), so we need to use string match -nocase to help out:
set files [lmap f [glob *.html] {
expr {[string match -nocase ${channel}.html $f] ? $f : [continue]}
}]
if {[llength $files] == 1} {
set channel_file [lindex $files 0]
} else {
# Oh no! Ambiguity!
}
That uses lmap from Tcl 8.6; earlier versions of Tcl should use this instead:
set files {}
foreach f [glob *.html] {
if {[string match -nocase ${channel}.html $f]} {
lappend files $f
}
}
if {[llength $files] == 1} {
set channel_file [lindex $files 0]
} else {
# Oh no! Ambiguity!
}
Pick a filename case (#channel.html, #Channel.html or #CHANNEL.HTML) and use string tolower, string totitle or string toupper respectively on filename_here. Then use that value for all file operations.
An lsearch filter on glob can be used to perform a case-insensitive search for a particular file name, e.g.
% lsearch -nocase -all -inline -glob [glob ./*] {*/myfile.txt}
./myFile.txt ./Myfile.txt ./MYFILE.txt
A sanity check using llength on the lsearch result above can be used to flag an error in case more than one file name is returned.

Grep the word inside double quote

How can I extract a word inside a double quote inside a file?
e.g.
variable "xxx"
Reading a text file into Tcl is just this:
set fd [open $filename]
set data [read $fd] ;# Now $data is the entire contents of the file
close $fd
To get the first quoted string (under some assumptions, notably a lack backslashed double quote characters inside the double quotes), use this:
if {[regexp {"([^""]*)"} $data -> substring]} {
# We found one, it's now in $substring
}
(Doubling up the quote in the brackets is totally unnecessary — only one is needed — but it does mean that the highlighter does the right thing here.)
The simplest method of finding all the quoted strings is this:
foreach {- substring} [regexp -inline -all {"([^""]*)"} $data] {
# One of the substrings is $substring at this point
}
Notice that I'm using the same regular expression in each case. Indeed, it's actually good practice to factor such REs (especially if repeatedly used) into a variable of their own so that you can “name” them.
Combining all that stuff above:
set FindQuoted {"([^""]*)"}
set fd [open $filename]
foreach {- substring} [regexp -inline -all $FindQuoted [read $fd]] {
puts "I have found $substring for you"
}
close $fd
Internal Matching
If you're just looking for a regular expression, then you can use TCL's capture groups. For example:
set string {variable "xxx"}
regexp {"(.*)"} $string match group1
puts $group1
This will return xxx, discarding the quotes.
External Matching
If you want to match data in a file without having to handling reading the file into TCL directly, you can do that too. For example:
set match [exec sed {s/^variable "\(...\)"/\1/} /tmp/foo]
This will call sed to find just the parts of the match you want, and assign them to a TCL variable for further process. In this example, the match variable is set to xxx as above, but is operating on an external file rather than a stored string.
When you just want to find with grep all words in quotes in a file and do something with the words, you do something like this (in a shell):
grep -o '"[^"]*"' | while read word
do
# do something with $word
echo extracted: $word
done