All sections
The cards come in more than one shape: a command to explain, a snippet with a fault to find, and a description whose command you name. A missed card returns later in the round and starts the schedule over. A card answered correctly moves up the schedule and next appears after 1, 3, 7, then 21 days. Keys 1 to 4 choose an option, h shows a hint, and Enter moves on. A hint keeps the card where it is on the schedule. An ordering card is answered by clicking its items in order, and one wrong click ends it. Time values are in nanoseconds. Progress is stored in this browser.
All 71 cards in this deck, with answers
-
set period 2.0 set half [expr {$period / 2}] puts "half is $half"Brackets run a command and substitute its result; quotes substitute variables.
Hint: Three kinds of bracket appear here. Ask what each one does before the command runs.
Square brackets are the command ones. Substitution happens once, before the command runs:
[...]is replaced by the result of the command inside it, and$nameby the variable's value. Braces,{...}, suppress both, which is why they appear around expressions and procedure bodies. Tcl reference -
set n 4 set a [expr {$n * 2}] set b [expr $n * 2]Both work here, but the braced form is safer and faster.
Hint: Both give 8. Ask what happens to the expression text before expr sees it.
Without braces the substitution happens first, so
exprreceives the values as text and must parse them again. That is slower, and a value that contains an operator or a space can change the expression's meaning. Braced expressions are the rule in tool scripts. Tcl reference -
set x 0.5 set y [expr {$x + 1}]Values are strings; `expr` is what interprets them as numbers.
Hint: Ask what type x has before expr looks at it.
Everything in Tcl is a string at the language level, and commands interpret it. This is why arithmetic needs
expr, why comparing numbers as strings misleads, and why a stray space in a value can turn into two arguments. Tcl reference -
incr count append names " $cell"Adds one to a numeric variable, and adds text to the end of a string.
Hint: Two shorthands. Ask what each saves you writing.
incrtakes an optional step, soincr count 5adds five and a negative value subtracts.appendgrows a string in place; the list equivalent islappend, which keeps list structure instead of concatenating text. Tcl reference -
set cells {} foreach c [get_cells *] { lappend cells [get_name $c] }Appends each name as one element, keeping the list structure intact.
Hint: Ask what lappend does that set with a concatenation would not.
A list is not just text with spaces:
lappendquotes an element that contains spaces or brackets so it stays a single element. Building a list by string concatenation breaks as soon as a name contains a bracket, which hierarchical names often do. Tcl reference -
set corners {slow typical fast} set first [lindex $corners 0] set last [lindex $corners end]Indexes from 0, and `end` names the last element.
Hint: Note where counting starts, and the word used for the other end.
endand arithmetic on it, such asend-1, work in every list command that takes an index.llengthgives the count, andlrangetakes a span, solrange $corners 1 enddrops the first element. Tcl reference -
lsort -real -decreasing $slacksSorts numerically and largest first, rather than as text ascending.
Hint: Ask what the default comparison would do to numbers.
Sorting defaults to text order, where 10 comes before 9.
-realand-integercompare numerically,-uniqueremoves duplicates, and-commandsorts with your own procedure, which is how a list of objects is sorted by a property. Tcl reference -
lassign [split $line ":"] rule value set parts [split "metal1 metal2 metal3"]Assigns list elements to named variables, and splits text into a list.
Hint: One takes a list apart into variables; the other makes a list from text.
splitcuts text at every character of the separator set, defaulting to whitespace;joinis its inverse.lassignunpacks a list positionally, which is the readable way to take apart a line already parsed into fields. Tcl reference -
if { $slack < 0 } { puts "failing" } else { puts "met" }The braces keep the condition unsubstituted so `if` evaluates it as an expression.
Hint: Ask why the condition is in braces rather than quotes.
ifevaluates its condition the wayexprdoes, so the same rule applies: braces stop the text being substituted first. Note also that} else {must sit on the line the brace closes, since a newline would end the command. Tcl reference -
foreach {name value} $pairs { puts "$name = $value" }Takes two elements per pass, binding them to the two names.
Hint: Two variable names in braces. Ask how many elements each pass consumes.
A flat list of alternating keys and values is a common shape in tool output, and this is how it is walked.
foreachcan also take several lists at once, stepping through them in parallel. Tcl reference -
switch $stage { place { global_placement } route { global_route } default { error "unknown stage: $stage" } }Runs the branch that matches, and default catches anything else.
Hint: Ask what the last branch is for, and what happens without it.
Only the matching branch runs. Without a
defaultan unmatched value does nothing at all, silently, which is how a typo in a stage name becomes a script that appears to work. Patterns are glob-style unless-exactor-regexpis given. Tcl reference -
proc report_stage {stage {verbose 0}} { puts "stage $stage" if { $verbose } { report_design_area } }Defines a command whose second argument is optional, defaulting to 0.
Hint: Read the second parameter: it is written differently from the first.
A parameter written as a two-element list gives a default. The special name
argscollects any remaining arguments into a list, which is how a wrapper passes options through to the command it wraps. Tcl reference -
proc count_cell {} { global cell_count incr cell_count }Makes the global variable visible inside the procedure.
Hint: Ask what a procedure can see by default.
A procedure's variables are local unless declared otherwise, so a script that sets a variable at the top level and expects a procedure to see it is a common bug.
upvarlinks to a caller's variable by name, which is how a procedure modifies a variable passed to it. Tcl reference -
proc add_to {listVar item} { upvar 1 $listVar l lappend l $item }Links the caller's variable to a local name, so changes reach the caller.
Hint: The procedure is given a variable's name rather than its value.
Tcl passes arguments by value, so modifying a list means passing its name and linking to it. The
1is how many levels up to look, almost always the immediate caller.uplevelis the same idea for running a script in the caller's scope. Tcl reference -
if { [string match "*_reg*" $name] } { ... }Glob-style matching, where `*` matches any text and `?` one character.
Hint: Ask which pattern language this is: the shell's or a regular expression's.
Glob patterns are the same ones the object-access commands take, which is why
get_cells *_reg*feels familiar. For anything with alternation or capture,regexpis the tool, at the cost of a denser pattern. Tcl reference -
if { [regexp {^(\w+)/(\w+)$} $pin -> inst port] } { ... }Captures the two groups into inst and port, discarding the whole match.
Hint: Three variables follow the pattern. Ask what the first one receives.
The first variable after the string takes the whole match, and
->is the conventional throwaway name for it. The pattern is in braces so Tcl does not substitute the backslashes before the regular-expression engine sees them. Tcl reference -
set out [string map {"/" "_" "\[" "" "\]" ""} $name]Replaces each key with its value, so the name becomes filename-safe.
Hint: Ask what the pairs in the list mean.
This is the quick way to make a hierarchical pin name usable as a file name, without a regular expression. The replacements are applied in one pass, so an earlier pair cannot match text produced by a later one. Tcl reference
-
puts [format "%-30s %8.3f" $endpoint $slack]Formats a padded name and a slack with three decimals, so columns line up.
Hint: Two field specifications. Ask what the numbers before them control.
The minus sign left-justifies, the first number is the field width, and the second is the precision. Reports that line up are read far faster than ones that do not, which matters when a script's output is scanned by eye across hundreds of rows. Tcl reference
-
dict set metrics wns -0.06 dict set metrics tns -2.18 puts [dict get $metrics wns]A dictionary, which is a value held in an ordinary variable.
Hint: Ask what the value of the variable metrics actually is.
A dictionary is a list of alternating keys and values. Because it is a value, it can be passed to a procedure, returned, and stored in a list, which an array cannot.
dict foriterates it, anddict existstests a key without the error thatdict getwould raise. Tcl reference -
set area(nand2) 0.798 foreach cell [array names area] { puts "$cell $area($cell)" }An array, which stays in the interpreter and cannot be passed by value.
Hint: Compare it with a dictionary: ask what you cannot do with this one.
Arrays predate dictionaries and are still everywhere in tool scripts. Passing one to a procedure needs
upvaron its name.array nameslists the keys, in no defined order, andarray getflattens it into a list that a dictionary can be built from. Tcl reference -
set def_file $::env(RESULTS_DIR)/6_final.defReads an environment variable exported by the flow's makefile.
Hint: Ask where a make-driven flow puts the values its scripts need.
envis an array holding the process environment, here the variables the makefile exported; and::names the global namespace explicitly so it works inside a procedure too. This is exactly how OpenROAD-flow-scripts passes itsconfig.mkvariables into every Tcl stage script. Tcl reference -
set fh [open $report w] puts $fh "slack $slack" close $fhOpens the file for writing, writes a line to it, and closes the handle.
Hint: Note the extra argument to puts, and what open returns.
putswrites to standard output unless given a channel first. The mode isrto read,wto truncate and write, andato append. Forgettingclosecan leave the last buffered lines unwritten if the script exits abruptly. Tcl reference -
if { [file exists $odb] } { set stem [file rootname [file tail $odb]] }Tests the path exists, then strips the directory and extension.
Hint: One command, several jobs, chosen by the word after it.
file existsis the test andtailwithrootnamedoes the stripping.fileis an ensemble:taildrops the directory,dirnamekeeps it,rootnamedrops the extension,extensionkeeps it, andjoinbuilds a path.file existsbefore reading is the usual guard in a flow script. Tcl reference -
set n [exec grep -c "VIOLATED" $report]Runs the program and returns what it printed, as the command's result.
Hint: Ask where the output of the program goes.
A non-zero exit status raises an error, so
execis usually wrapped incatchwhen the program's failure is expected, asgrepfinding nothing is. Redirections and pipes can be written in the arguments. Tcl reference -
source $::env(SCRIPTS_DIR)/load.tclRuns the file's commands in the current scope, as if typed there.
Hint: Ask what scope the sourced file's commands and variables end up in.
Everything the file sets is visible afterwards, and a second
sourceruns it again. This is how the flow shares a common setup across stage scripts; it is also why a sourced file that sets a variable can surprise the script that sourced it. Tcl reference -
if { [catch { read_spef $spef } err] } { puts "no parasitics: $err" }Runs the script and returns non-zero if it failed, with err set.
Hint: Ask what catch returns, and what the second variable receives.
The message goes into the named variable.
catchreturns 0 on success, so theifabove reads as "if this failed". Wrapping a whole stage incatchhides real failures, so it is used narrowly, around the step whose failure is expected and handled. Tcl reference -
if { ![file exists $lib] } { error "missing library: $lib" }Raises an error that stops the script unless a caller catches it.
Hint: Ask what happens to the script after this line runs.
Failing early with a clear message is far better than continuing on missing input and failing later in a stage that cannot explain why. Flow scripts use this to check their inputs before spending an hour on a run that cannot succeed. Tcl reference
-
if { [info exists ::env(CTS_ARGS)] } { set args $::env(CTS_ARGS) }Tests whether the variable is set, since reading an unset one is an error.
Hint: Ask what reading an unset variable does.
An optional setting is exactly this shape: use it if it exists, otherwise keep the default.
infoalso answers questions about procedures, commands, and the call stack, which is how a script reports where it is. Tcl reference -
set cells [get_cells *_reg*] puts [llength $cells]The tool returns objects, which not every list command handles.
Hint: Ask whether what the tool returns is really a Tcl list.
What comes back is a collection of objects. They carry properties that a name cannot, which is why
get_propertyandget_full_nameexist. Some tools return a collection type that needs its own commands, so mixing list commands with tool objects is a common source of confusion when a script moves between tools. Tcl reference -
set args "" if { $::env(GPL_TIMING_DRIVEN) } { append args " -timing_driven" } global_placement {*}$argsExpands the string into separate arguments instead of one long one.
Hint: Read the last line: something is done to the variable before the command sees it.
Without
{*}, a command receives" -timing_driven"as one argument and rejects it. Building a command's options conditionally and expanding them is the standard way a flow turns variables into tool arguments. Tcl reference -
set period_ns 2.0 create_clock -name clk -period $period_ns [get_ports clk]The number has no unit in Tcl; the tool reads it in library units.
Hint: Ask what the tool does with the number, and where its unit comes from.
Library units are usually nanoseconds, but not always. A script can hold a number that means something different to the tool than to its author, and nothing will complain.
report_unitsshows what the tool is using, andset_unitsrecords the intent in a constraint file. Tcl reference -
foreach path [find_timing_paths -path_delay max -group_path_count 10] { puts "[get_property $path slack]" }Iterates the path objects the tool returns, reading a property from each.
Hint: Ask why the script asks for objects rather than reading a report.
Parsing a printed report is brittle: its format changes between versions. Asking for objects and reading properties is stable, and it is why reporting commands usually have a
find_counterpart meant for scripts. Tcl reference -
if { ![design_is_routed] } { error "routing did not finish" }Stops the flow before a later stage runs on a design that is not ready.
Hint: Ask what this protects the rest of the flow from.
A stage that runs on an unfinished design either fails confusingly much later or produces results that look plausible and are not. Checks between stages turn that into one clear failure at the point where something actually went wrong. Tcl reference
-
set name clk puts "the clock is $name" puts {the clock is $name}Quotes substitute variables and commands; braces pass the text through.
Hint: Read the second line and the third as the interpreter would.
The second line prints the clock is clk and the third prints the dollar sign and the name. This is the single most useful rule in the language: braces mean literally this, quotes mean fill it in. It is why procedure bodies and expressions are braced. Tcl reference
-
set name clk expr { $name == "clk" } expr { $name eq "clk" }Both compare here, but only the second is always a string comparison.
Hint: One of the two operators works on numbers first.
The numeric operator converts when it can, so comparing 1.0 with 1 is true and comparing 0x10 with 16 is true. That is rarely what a script comparing names wants, and it is a classic source of a condition that is true for the wrong reason. Use
eqandnefor text. Tcl reference -
set stage place puts "${stage}_done"The braces mark where the variable name ends.
Hint: Ask where the interpreter would decide the variable name ends.
Without them the interpreter looks for a variable called
stage_doneand fails. A name runs to the first character that cannot be part of one, and an underscore can be, which is why this bites when building file names. Tcl reference -
set ::top_module gcd puts $::env(HOME)The variable at the top level, reachable from inside any procedure.
Hint: Two colons in front of a name. Ask which variable is meant.
A procedure sees only its own variables unless it says otherwise, and the leading colons name the global namespace directly, which avoids having to declare it. The environment array is the common case, since a flow is usually configured through it. Tcl reference
-
set corners {slow typical fast} lsearch $corners typical lsearch -exact -not -inline -all $corners typicalThe position of a match, or minus one.
Hint: The plain form answers where, not whether.
The plain call returns an index, so a test reads
if {[lsearch $l $x] >= 0}. Options change both the matching and the result:-exact,-glob, and-regexpchoose how to match,-allreturns every index, and-inlinereturns the elements instead. Tcl reference -
set corners {slow typical fast} lreplace $corners 1 1 nominal puts $cornersThe original is unchanged, since the command returns a new list.
Hint: Ask what the last line prints.
Every list command except
lappendandlsetreturns a value and leaves its argument alone, so the result has to be assigned. Forgetting this is why a filtered list comes out identical to the one it was built from. Tcl reference -
join {metal1 metal2 metal3} ", " concat {a b} {c d}The first makes one string with a separator; the second merges lists.
Hint: One produces a string, the other a list.
Joining is how a report line or a comma-separated argument is built, and
splitis its inverse. Merging produces a flat list, which is the difference fromlappend, since appending a list adds it as one element. Tcl reference -
set pin {dpath/a_reg[9]/D} report_slack $pin eval "report_slack $pin"The second substitutes the name a second time, so the brackets run as a command.
Hint: One of the two lines lets the brackets be seen by the interpreter again.
Substitution happens once per evaluation, so passing a variable directly is safe however strange the name is. Building a command as a string and evaluating it exposes every special character again. Where a command must be built,
listquotes each part correctly. Tcl reference -
for {set i 0} {$i < $n} {incr i} { ... } while {[gets $fh line] >= 0} { ... }Both are ordinary commands whose arguments are braced scripts.
Hint: Count the pieces of the first line and ask what each one is.
The loop takes four arguments: a start script, a test expression, a step script, and a body. Nothing here is special syntax, which is why the opening brace must sit on the same line as the command: a newline would end it. Tcl reference
-
foreach c $cells { if { [string match "FILLER*" $c] } { continue } if { $found } { break } }The first goes to the next item; the second ends the loop entirely.
Hint: One skips the rest of this pass, the other leaves the loop.
Both are implemented as return codes rather than as jumps, which is why they work inside a braced body but not inside a procedure called from one. A procedure that wants to stop its caller's loop has to return a value the caller tests. Tcl reference
-
switch -glob -- $name { *_reg* - *_ff* { set kind register } default { set kind logic } }Glob matching, with the first pattern falling through to the next body.
Hint: One branch has a body of a single hyphen.
A body of one hyphen means use the next one, which is how several patterns share code. The double hyphen ends the options, so a value beginning with one is not mistaken for a switch. Without
-globthe match is exact, and-regexpis also available. Tcl reference -
proc report {stage args} { puts "$stage: $args" } report place -verbose -limit 10Whatever is left over arrives as a list in the final parameter.
Hint: The last parameter has a name the language treats specially.
This is how a wrapper passes options through to the command it calls, usually with
{*}$argsso the list is expanded back into separate arguments rather than passed as one. It is the standard way to wrap a tool command and add a default. Tcl reference -
proc worst {slacks} { lindex [lsort -real $slacks] 0 }The value of the last command is returned.
Hint: There is no return statement, and yet the procedure is useful.
An explicit
returnis still clearer, and it is needed to leave early or to return something other than the last value. Its option form sets the code as well, soreturn -code errorraises an error the caller can catch, exactly aserrordoes. Tcl reference -
namespace eval flow { variable count 0 proc reset {} { variable count; set count 0 } } flow::resetA named scope for procedures and variables.
Hint: Ask what would happen if the procedure were called `reset` at the top level.
A tool's interpreter already holds hundreds of commands, and a procedure named the same as one of them replaces it silently for the rest of the session. Namespacing a script's own procedures is the way to stay clear of that, and
variableis how a procedure reaches the namespace's data. Tcl reference -
regsub -all {\[(\d+)\]} $name {_\1} flatReplaces every match, writing the result into the named variable.
Hint: The last argument is a variable name, and the pattern has a group in it.
The return value is the number of substitutions made, and the new string goes into the variable given last; without that argument the string is returned instead. In the replacement, a backslash and a digit stand for the matching group, so bus bits become underscores here. Tcl reference
-
set nums [regexp -all -inline {-?\d+\.\d+} $line]A list of every match in the line, instead of one or zero.
Hint: Ask what is returned when the match variables are left out.
This is the compact way to pull the numbers out of a report line without a loop. With capture groups, each match contributes the whole match and then each group, so the list has to be read in strides. Tcl reference
-
string trim $line string first ":" $line string range $line 0 $iStrip the whitespace, find a character's position, and take a slice.
Hint: Three small operations that together take a line apart.
Positions are counted from zero and
endmay be used in place of a number, as instring range $line 0 end-1. Between these and the pattern commands, most report parsing needs no regular expression at all. Tcl reference -
dict for {key value} $metrics { puts [format "%-12s %s" $key $value] } dict exists $metrics wns dict keys $metricsWalks every pair in insertion order, with the keys available separately.
Hint: Ask what order the pairs come out in.
Insertion order is guaranteed, which is what makes a dictionary good for a report whose rows should stay in the order they were built. Testing with
dict existsavoids the error that fetching a missing key raises. Tcl reference -
set metrics [dict create wns -0.06] set area(nand2) 0.798A dictionary is a value; an array is a variable.
Hint: Ask which of the two can be returned from a procedure.
Because an array is a collection of variables, a procedure has to reach it with
upvaror pass it asarray get. A dictionary is an ordinary string, so it can be passed, returned, and nested. Arrays remain useful for very large tables that are updated in place. Tcl reference -
dict set runs base place wns -0.06 dict get $runs base place wnsA path of keys into a dictionary of dictionaries.
Hint: Count the words between the command and the value.
Intermediate levels are created as needed on a set, which makes this a tidy way to collect results per run and per stage. Reading a path that does not exist raises an error, so
dict existstakes a path too. Tcl reference -
dict incr counts $type dict lappend byType $type $violationCount into a key, and collect a list under a key.
Hint: Both work on a key that may not be there yet.
This is the whole of a violation summary: walk the report, increment by type, and keep the instances under the same key. Neither needs the key to exist first, which removes the usual test before each update. Tcl reference
-
set fh [open $report r] while { [gets $fh line] >= 0 } { ... } close $fhThe line's length, which is negative only at the end of the file.
Hint: Ask what the command returns, given what the test compares it with.
An empty line has length zero, so testing for greater than or equal to zero is right and testing for greater than zero silently stops at the first blank line. Reading a whole file with
readis simpler but loads all of it, which matters on a large report. Tcl reference -
if { [catch { exec grep -c VIOLATED $rpt } out] } { set out 0 }A non-zero exit status raises an error, so it has to be caught.
Hint: The command finds nothing. Ask what that does to the script.
Anything the command writes to its error stream, and any non-zero status, becomes a Tcl error. That is why counting matches is wrapped like this, and why
2>@1or a redirect to a file appears in scripts that expect warnings from the command they run. Tcl reference -
set out [file join $::env(RESULTS_DIR) reports timing.rpt] file mkdir [file dirname $out]Builds a path correctly and creates the directories leading to it.
Hint: Ask what this avoids, compared with building the path with slashes.
Joining handles a trailing separator and an absolute component correctly, which string concatenation does not. Making a directory creates every missing level and does not complain when it is already there, so no test is needed first. Tcl reference
-
try { set fh [open $f r] process $fh } finally { close $fh }The last block runs either way, error or not.
Hint: Ask what runs if the middle line raises an error.
The error still propagates; the block only guarantees the cleanup. There is also
trapfor handling a particular kind of error. In a tool script this is how a report file, a redirect, or a changed setting is restored whatever happens. Tcl reference -
set code [catch { risky } result opts]The status, the result or message, and an error dictionary.
Hint: Three things come back. Ask what the third one is for.
The options hold the error code, the stack trace, and the return code, which is what a wrapper needs to re-raise an error faithfully with
return -options $opts $result. Catching and reporting only the message throws away everything that makes the failure findable. Tcl reference -
puts $::errorInfoThe stack trace of the last error.
Hint: Ask what more this holds than the message the error carried.
A tool that prints only a one-line message leaves this behind, and it usually names the procedure and the line. A script that catches errors should record it, since the trace is gone as soon as the next error replaces it. Tcl reference
-
set regs [get_cells *_reg*] llength $regs sizeof_collection $regsA collection is not a list, so counting it needs the tool's own command.
Hint: Ask what the second line counts, in a tool that returns objects.
Commercial timers and place-and-route tools hand back a collection, which is a handle to a set of objects rather than a string. Counting it with a list command gives one, or nonsense. The open tools return ordinary Tcl lists, which is why a script moved between them needs care here. Tcl reference
-
foreach_in_collection c [get_cells *] { puts [get_object_name $c] }The tool's own loop over a collection, and the way to get an object's name.
Hint: Two commands here are not Tcl's. Ask what each replaces.
The loop variable holds an object, not a string, so printing it directly gives an internal identifier. Where the open tools let
foreachwork over a list of objects, the commercial ones need this form, and it is the commonest thing to change when a script crosses between them. Tcl reference -
set ep [lindex $endpoints 0] report_slack $ep report_slack "dpath/a_reg[9]/D"Passing the object avoids the brackets in the name being substituted.
Hint: Look at what the quoted name contains.
Inside quotes the brackets are still a command substitution, so the name has to be braced or escaped. Keeping objects in variables and passing them straight through sidesteps the whole problem, which matters because bus bits and generated names are full of special characters. Tcl reference
-
redirect -variable txt { report_timing } with_output_to_variable txt { report_checks }Capture a report's text into a variable instead of printing it.
Hint: Both do the same job, under different names.
A report command prints and returns nothing, so parsing its output means capturing it first. The first spelling is the commercial one and the second is OpenSTA's. Both take the command as a script, so it must be braced, not built as a string. Tcl reference
-
foreach c $cells { if { [get_property [get_cells $c] is_buffer] } { ... } }It queries the database once per cell, not once in total.
Hint: Ask what the inner call does on every pass of the loop.
A database query costs far more than a Tcl operation, and a loop over a million instances turns a second into an hour. The fix is to ask once, with a filter in the query, and loop over what comes back. This is the usual reason a constraint script takes longer than the tool stage it configures. Tcl reference
-
set half [expr $period / 2]The value is substituted into the expression before it is parsed.
Hint: Ask what the expression looks like after substitution, if the variable holds text.
With a plain number it works. If the variable holds
2+1, the expression becomes2+1 / 2, which is not what was meant, and a value from a file or a tool can hold anything. Bracing the expression also lets it be compiled once rather than reparsed on every pass of a loop. Tcl reference -
if { $slack < 0 } { puts "failing" }The newline ends the command, so the body is not passed to it.
Hint: Remember that the body is an argument, not a block.
A command ends at a newline, so this is a conditional with no body, followed by a script that the interpreter tries to run as a command. The opening brace has to sit on the same line, which is why every Tcl script looks the way it does. Tcl reference
-
set count 0 proc bump {} { incr count }The procedure has its own scope, so it does not see that variable.
Hint: Ask which variable the procedure is reaching for.
A procedure sees only its own variables and those it names with
global,variable, orupvar. As written it fails, since there is no local variable to increment. The clearer fix in a tool script is often to pass the value in and return the new one. Tcl reference -
set reports [glob $dir/*.rpt]Matching nothing is an error, which stops the script.
Hint: Ask what happens on the run where the stage produced nothing.
The command raises an error when no file matches, which is right for a required file and wrong for an optional one. Adding
-nocomplainreturns an empty list instead. Unlike a shell, nothing else expands the pattern: it is matched by this command alone. Tcl reference -
You want to walk a very large report one line at a time, without holding the whole file in memory, and without an extra empty pass at the end.while { [gets $fh line] >= 0 } { ... }
Hint: One of these tests the wrong thing, and runs the body once too often.
The end of a file is only known once a read has failed, so testing for it first runs the body one extra time with a stale line. Reading the whole file and splitting it is correct but loads everything, which matters on a report of millions of lines. Tcl reference
-
Your procedure is given the name of a variable belonging to its caller, and it has to change that variable rather than a copy.upvar 1 $varName local
Hint: The procedure needs a local name that stands for the caller's variable.
Linking the names makes every later use of the local name act on the caller's variable. Declaring it global reaches the top level instead, which is wrong when the caller is another procedure, and assigning through the name only works if the variable happens to be visible. Tcl reference