[language/tcl-en]Extensive edit of the Tcl document (#2731)

* Rework some examples.

* Small change to description of iterator commands.

* Adjust whitespace.

* Adjust whitespace again.

* Various changes to wording and grammar

* Rather extensive editing of the entire document.

* trivial change of one variable name
This commit is contained in:
Poor Yorick 2017-05-19 15:41:48 -06:00 committed by ven
parent d30d6f69ca
commit c809a163b4

View File

@ -1,21 +1,21 @@
--- ---
language: Tcl language: Tcl
contributors: contributors:
- ["Poor Yorick", "http://pooryorick.com/"] - ["Poor Yorick", "https://pooryorick.com/"]
filename: learntcl.tcl filename: learntcl.tcl
--- ---
Tcl was created by [John Ousterhout](http://wiki.tcl.tk/John Ousterout) as a Tcl was created by [John Ousterhout](https://wiki.tcl.tk/John%20Ousterout) as a
reusable scripting language for chip design tools he was creating. In 1997 he reusable scripting language for circuit design tools that he authored. In 1997 he
was awarded the [ACM Software System was awarded the [ACM Software System
Award](http://en.wikipedia.org/wiki/ACM_Software_System_Award) for Tcl. Tcl Award](https://en.wikipedia.org/wiki/ACM_Software_System_Award) for Tcl. Tcl
can be used both as an embeddable scripting language and as a general can be used both as an embeddable scripting language and as a general
programming language. It can also be used as a portable C library, even in programming language. It can also be used as a portable C library, even in
cases where no scripting capability is needed, as it provides data structures cases where no scripting capability is needed, as it provides data structures
such as dynamic strings, lists, and hash tables. The C library also provides such as dynamic strings, lists, and hash tables. The C library also provides
portable functionality for loading dynamic libraries, string formatting and portable functionality for loading dynamic libraries, string formatting and
code conversion, filesystem operations, network operations, and more. code conversion, filesystem operations, network operations, and more. Various
Various features of Tcl stand out: features of Tcl stand out:
* Convenient cross-platform networking API * Convenient cross-platform networking API
@ -30,30 +30,29 @@ Various features of Tcl stand out:
* A threading model recognized as robust and easy to use * A threading model recognized as robust and easy to use
If Lisp is a list processor, then Tcl is a string processor. All values are Tcl has much in common with Lisp, but instead of lists, Tcl uses strings as the
strings. A list is a string format. A procedure definition is a string currency of the language. All values are strings. A list is a string with a
format. To achieve performance, Tcl internally caches structured defined format, and the body of a procedure (a script) is also a string rather
representations of these values. The list commands, for example, operate on than a block. To achieve performance, Tcl internally caches structured
representations of these values. list routines, for example, operate on
the internal cached representation, and Tcl takes care of updating the string the internal cached representation, and Tcl takes care of updating the string
representation if it is ever actually needed in the script. The copy-on-write representation if it is ever actually needed in the script. The copy-on-write
design of Tcl allows script authors can pass around large data values without design of Tcl allows script authors to pass around large data values without
actually incurring additional memory overhead. Procedures are automatically actually incurring additional memory overhead. Procedures are automatically
byte-compiled unless they use the more dynamic commands such as "uplevel", byte-compiled unless they use the more dynamic routines such as "uplevel",
"upvar", and "trace". "upvar", and "trace".
Tcl is a pleasure to program in. It will appeal to hacker types who find Lisp, Tcl is a pleasure to program in. It will appeal to hacker types who find Lisp,
Forth, or Smalltalk interesting, as well as to engineers and scientists who Forth, or Smalltalk interesting, as well as to engineers and scientists who
just want to get down to business with a tool that bends to their will. Its just want to get down to business with a tool that bends to their will. Its
discipline of exposing all programmatic functionality as commands, including discipline of exposing all programmatic functionality as routines, including
things like loops and mathematical operations that are usually baked into the things like looping and mathematical operations that are usually baked into the
syntax of other languages, allows it to fade into the background of whatever syntax of other languages, allows it to fade into the background of whatever
domain-specific functionality a project needs. It's syntax, which is even domain-specific functionality a project needs. Its syntax, which is even
lighter that that of Lisp, just gets out of the way. lighter that that of Lisp, just gets out of the way.
```tcl ```tcl
#! /bin/env tclsh #! /bin/env tclsh
@ -61,70 +60,75 @@ lighter that that of Lisp, just gets out of the way.
## 1. Guidelines ## 1. Guidelines
############################################################################### ###############################################################################
# Tcl is not Bash or C! This needs to be said because standard shell quoting # Tcl is not Sh or C! This needs to be said because standard shell quoting
# habits almost work in Tcl and it is common for people to pick up Tcl and try # habits almost work in Tcl and it is common for people to pick up Tcl and try
# to get by with syntax they know from another language. It works at first, # to get by with syntax they know from another language. It works at first,
# but soon leads to frustration with more complex scripts. # but soon leads to frustration when scripts become more complex.
# Braces are just a quoting mechanism, not a code block constructor or a list # Braces are a quoting mechanism, not syntax for the construction of code
# constructor. Tcl doesn't have either of those things. Braces are used, # blocks or lists. Tcl doesn't have either of those things. Braces are used to
# though, to escape special characters in procedure bodies and in strings that # escape special characters, which makes them well-suited for quoting procedure
# are formatted as lists. # bodies and strings that should be interpreted as lists.
############################################################################### ###############################################################################
## 2. Syntax ## 2. Syntax
############################################################################### ###############################################################################
# Every line is a command. The first word is the name of the command, and # A script is made up of commands delimited by newlines or semiclons. Each
# subsequent words are arguments to the command. Words are delimited by # command is a call to a routine. The first word is the name of a routine to
# whitespace. Since every word is a string, in the simple case no special # call, and subsequent words are arguments to the routine. Words are delimited
# markup such as quotes, braces, or backslash, is necessary. Even when quotes # by whitespace. Since each argument is a word in the command it is already a
# are used, they are not a string constructor, but just another escaping # string, and may be unquoted:
# character. set part1 Sal
set part2 ut; set part3 ations
set greeting1 Sal
set greeting2 ut
set greeting3 ations
#semicolon also delimits commands # a dollar sign introduces variable substitution:
set greeting1 Sal; set greeting2 ut; set greeting3 ations set greeting $part1$part2$part3
# Dollar sign introduces variable substitution # When "set" is given only the name of a variable, it returns the
set greeting $greeting1$greeting2$greeting3 # value of that variable:
set part3 ;# Returns the value of the variable.
# Bracket introduces command substitution. The result of the command is # Left and right brackets embed a script to be evaluated for a result to
# substituted in place of the bracketed script. When the "set" command is # substitute into the word:
# given only the name of a variable, it returns the value of that variable. set greeting $part1$part2[set part3]
set greeting $greeting1$greeting2[set greeting3]
# Command substitution should really be called script substitution, because an # An embedded script may be composed of multiple commands, the last of which provides
# entire script, not just a command, can be placed between the brackets. The # the result for the substtution:
# "incr" command increments the value of a variable and returns its value.
set i 0
set greeting $greeting[ set greeting $greeting[
incr i incr i
incr i incr i
incr i incr i
] ]
# i is now 3 puts $greeting ;# The output is "Salutations3"
# backslash suppresses the special meaning of characters # Every word in a command is a string, including the name of the routine, so
# substitutions can be used on it as well. Given this variable
# assignment,
set action pu
# , the following three commands are equivalent:
puts $greeting
${action}ts $greeting
[set action]ts $greeting
# backslash suppresses the special meaning of characters:
set amount \$16.42 set amount \$16.42
# backslash adds special meaning to certain characters # backslash adds special meaning to certain characters:
puts lots\nof\n\n\n\n\n\nnewlines puts lots\nof\n\n\n\n\n\nnewlines
# A word enclosed in braces is not subject to any special interpretation or # A word enclosed in braces is not subject to any special interpretation or
# substitutions, except that a backslash before a brace is not counted when # substitutions, except that a backslash before a brace is not counted when
# looking for the closing brace # looking for the closing brace:
set somevar { set somevar {
This is a literal $ sign, and this \} escaped This is a literal $ sign, and this \} escaped
brace remains uninterpreted brace remains uninterpreted
@ -132,40 +136,44 @@ set somevar {
# In a word enclosed in double quotes, whitespace characters lose their special # In a word enclosed in double quotes, whitespace characters lose their special
# meaning # meaning:
set name Neo set name Neo
set greeting "Hello, $name" set greeting "Hello, $name"
#variable names can be any string # A variable name can be any string:
set {first name} New set {first name} New
# The brace form of variable substitution handles more complex variable names # The braced form of variable substitution handles more complex variable names:
set greeting "Hello, ${first name}" set greeting "Hello, ${first name}"
# The "set" command can always be used instead of variable substitution # "set" can always be used instead of variable substitution, and can handle all
# variable names:
set greeting "Hello, [set {first name}]" set greeting "Hello, [set {first name}]"
# To promote the words within a word to individual words of the current # To unpack a list into the command, use the expansion operator, "{*}". These
# command, use the expansion operator, "{*}". # two commands are equivalent:
```
```tcl
set {*}{name Neo}
# is equivalent to
set name Neo set name Neo
set {*}{name Neo}
# An array is a special variable that is a container for other variables. # An array is a special variable that is a container for other variables.
set person(name) Neo set person(name) Neo
set person(gender) male set person(destiny) {The One}
set greeting "Hello, $person(name)" set greeting "Hello, $person(name)"
# A namespace holds commands and variables # "variable" can be used to declare or set variables. In contrast with "set",
# which uses both the global namespace and the current namespace to resolve a
# variable name, "variable" uses only the current namespace:
variable name New
# "namespace eval" creates a new namespace if it doesn't exist. A namespace
# can contain both routines and variables:
namespace eval people { namespace eval people {
namespace eval person1 { namespace eval person1 {
variable name Neo variable name Neo
@ -173,50 +181,102 @@ namespace eval people {
} }
# The full name of a variable includes its enclosing namespace(s), delimited by # Use two or more colons to delimit namespace components in variable names:
# two colons: namespace eval people {
set greeting "Hello $people::person1::name" set greeting "Hello $person1::name"
}
# Two or more colons also delimit namespace components in routine names:
proc people::person1::speak {} {
puts {I am The One.}
}
# Fully-qualified names begin with two colons:
set greeting "Hello $::people::person1::name"
############################################################################### ###############################################################################
## 3. A Few Notes ## 3. No More Syntax
############################################################################### ###############################################################################
# All other functionality is implemented via commands. From this point on, # All other functionality is implemented via routines. From this point on,
# there is no new syntax. Everything else there is to learn about Tcl is about # there is no new syntax. Everything else there is to learn about
# the behaviour of individual commands, and what meaning they assign to their # Tcl is about the behaviour of individual routines and what meaning they
# arguments. # assign to their arguments.
###############################################################################
## 4. Variables and Namespaces
###############################################################################
# Each variable and routine is associated with some namespace.
# To end up with an interpreter that can do nothing, delete the global # To end up with an interpreter that can do nothing, delete the global
# namespace. It's not very useful to do such a thing, but it illustrates the # namespace. It's not very useful to do such a thing, but it illustrates the
# nature of Tcl. # nature of Tcl. The name of the global namespace is actually the empty
namespace delete :: # string, but the only way to represent it is as a fully-qualified name. To
# try it out call this routine:
proc delete_global_namespace {} {
namespace delete ::
}
# Because "set" always keeps its eye on both the global namespace and the
# Because of name resolution behaviour, it's safer to use the "variable" # current namespace, it's safer to use "variable" to declare a variable or
# command to declare or to assign a value to a namespace. If a variable called # assign a value to a variable. If a variable called "name" already exists in
# "name" already exists in the global namespace, using "set" here will assign # the global namespace, using "set" here will assign a value to the global
# a value to the global variable instead of creating a new variable in the # variable instead of to a variable in the current namespace, whereas
# local namespace. # "variable" operates only on the current namespace.
namespace eval people { namespace eval people {
namespace eval person1 { namespace eval person1 {
variable name Neo variable name Neo
} }
} }
# Once a variable is declared in a namespace, [set] sees it instead of seeing
# an identically-named variable in the global namespace:
namespace eval people {
namespace eval person1 {
variable name
set name Neo
}
}
# But if "set" has to create a new variable, it always does it relative to the
# current namespace:
unset name
namespace eval people {
namespace eval person1 {
set name neo
}
}
set people::person1::name
# An absolute name always begins with the name of the global namespace (the
# empty string), followed by two colons:
set ::people::person1::name Neo
# Within a procedure, the "variable" links a variable in the current namespace
# into the local scope:
namespace eval people::person1 {
proc fly {} {
variable name
puts "$name is flying!"
}
}
# The full name of a variable can always be used, if desired.
set people::person1::name Neo
############################################################################### ###############################################################################
## 4. Commands ## 4. Built-in Routines
############################################################################### ###############################################################################
# Math can be done with the "expr" command. # Math can be done with the "expr":
set a 3 set a 3
set b 4 set b 4
set c [expr {$a + $b}] set c [expr {$a + $b}]
@ -226,54 +286,63 @@ set c [expr {$a + $b}]
# "http://wiki.tcl.tk/Brace%20your%20#%20expr-essions" for details. # "http://wiki.tcl.tk/Brace%20your%20#%20expr-essions" for details.
# The "expr" command understands variable and command substitution # "expr" understands variable and script substitution:
set c [expr {$a + [set b]}] set c [expr {$a + [set b]}]
# The "expr" command provides a set of mathematical functions # "expr" provides a set of mathematical functions:
set c [expr {pow($a,$b)}] set c [expr {pow($a,$b)}]
# Mathematical operators are available as commands in the ::tcl::mathop # Mathematical operators are available as routines in the ::tcl::mathop
# namespace # namespace:
::tcl::mathop::+ 5 3 ::tcl::mathop::+ 5 3
# Commands can be imported from other namespaces # Routines can be imported from other namespaces:
namespace import ::tcl::mathop::+ namespace import ::tcl::mathop::+
set result [+ 5 3] set result [+ 5 3]
# New commands can be created via the "proc" command. # Non-numeric values must be quoted, and operators like "eq" can be used to
# constrain the operation to string comparison:
set name Neo
expr {{Bob} eq $name}
# The general operators fall back to string string comparison if numeric
# operation isn't feasible:
expr {{Bob} == $name}
# "proc" creates new routines:
proc greet name { proc greet name {
return "Hello, $name!" return "Hello, $name!"
} }
#multiple parameters can be specified #multiple parameters can be specified:
proc greet {greeting name} { proc greet {greeting name} {
return "$greeting, $name!" return "$greeting, $name!"
} }
# As noted earlier, braces do not construct a code block. Every value, even # As noted earlier, braces do not construct a code block. Every value, even
# the third argument of the "proc" command, is a string. The previous command # the third argument to "proc", is a string. The previous command
# rewritten to not use braces at all: # can be rewritten using no braces:
proc greet greeting\ name return\ \"\$greeting,\ \$name!\" proc greet greeting\ name return\ \"\$greeting,\ \$name!\"
# When the last parameter is the literal value, "args", it collects all extra # When the last parameter is the literal value "args", all extra arguments
# arguments when the command is invoked # passed to the routine are collected into a list and assigned to "args":
proc fold {cmd args} { proc fold {cmd first args} {
set res 1
foreach arg $args { foreach arg $args {
set res [$cmd $res $arg] set first [$cmd $first $arg]
} }
return $res return $first
} }
fold ::tcl::mathop::* 5 3 3 ;# -> 45 fold ::tcl::mathop::* 5 3 3 ;# -> 45
# Conditional execution is implemented as a command # Conditional execution is implemented as a routine:
if {3 > 4} { if {3 > 4} {
puts {This will never happen} puts {This will never happen}
} elseif {4 > 4} { } elseif {4 > 4} {
@ -283,31 +352,40 @@ if {3 > 4} {
} }
# Loops are implemented as commands. The first, second, and third # Loops are implemented as routines. The first and third arguments to
# arguments of the "for" command are treated as mathematical expressions # "for" are treated as scripts, while the second argument is treated as
# an expression:
set res 0
for {set i 0} {$i < 10} {incr i} { for {set i 0} {$i < 10} {incr i} {
set res [expr {$res + $i}] set res [expr {$res + $i}]
} }
unset res
# The first argument of the "while" command is also treated as a mathematical # The first argument to "while" is also treated as an expression:
# expression
set i 0 set i 0
while {$i < 10} { while {$i < 10} {
incr i 2 incr i 2
} }
# A list is a specially-formatted string. In the simple case, whitespace is # A list is a string, and items in the list are delimited by whitespace:
# sufficient to delimit values
set amounts 10\ 33\ 18 set amounts 10\ 33\ 18
set amount [lindex $amounts 1] set amount [lindex $amounts 1]
# Whitespace in a list item must be quoted:
set inventory {"item 1" item\ 2 {item 3}}
# It's generally a better idea to use list routines when modifing lists:
lappend inventory {item 1} {item 2} {item 3}
# Braces and backslash can be used to format more complex values in a list. A # Braces and backslash can be used to format more complex values in a list. A
# list looks exactly like a script, except that the newline character and the # list looks exactly like a script, except that the newline character and the
# semicolon character lose their special meanings. This feature makes Tcl # semicolon character lose their special meanings, and there is no script or
# homoiconic. There are three items in the following list. # variable substitution. This feature makes Tcl homoiconic. There are three
# items in the following list:
set values { set values {
one\ two one\ two
@ -319,19 +397,19 @@ set values {
} }
# Since a list is a string, string operations could be performed on it, at the # Since, like all values, a list is a string, string operations could be
# risk of corrupting the formatting of the list. # performed on it, at the risk of corrupting the formatting of the list:
set values {one two three four} set values {one two three four}
set values [string map {two \{} $values] ;# $values is no-longer a \ set values [string map {two \{} $values] ;# $values is no-longer a \
properly-formatted listwell-formed list properly-formatted list
# The sure-fire way to get a properly-formmated list is to use "list" commands # The sure-fire way to get a properly-formatted list is to use "list" routines:
set values [list one \{ three four] set values [list one \{ three four]
lappend values { } ;# add a single space as an item in the list lappend values { } ;# add a single space as an item in the list
# Use "eval" to evaluate a value as a script # Use "eval" to evaluate a value as a script:
eval { eval {
set name Neo set name Neo
set greeting "Hello, $name" set greeting "Hello, $name"
@ -339,84 +417,94 @@ eval {
# A list can always be passed to "eval" as a script composed of a single # A list can always be passed to "eval" as a script composed of a single
# command. # command:
eval {set name Neo} eval {set name Neo}
eval [list set greeting "Hello, $name"] eval [list set greeting "Hello, $name"]
# Therefore, when using "eval", use [list] to build up a desired command # Therefore, when using "eval", , use "list" to build
# up the desired command:
set command {set name} set command {set name}
lappend command {Archibald Sorbisol} lappend command {Archibald Sorbisol}
eval $command eval $command
# A common mistake is not to use list functions when building up a command # A common mistake is not to use list functions when building up a command:
set command {set name} set command {set name}
append command { Archibald Sorbisol} append command { Archibald Sorbisol}
eval $command ;# There is an error here, because there are too many arguments \ try {
to "set" in {set name Archibald Sorbisol} eval $command ;# The error here is that there are too many arguments \
to "set" in {set name Archibald Sorbisol}
} on error {result eoptions} {
puts [list {received an error} $result]
}
# This mistake can easily occur with "subst":
# This mistake can easily occur with the "subst" command.
set replacement {Archibald Sorbisol} set replacement {Archibald Sorbisol}
set command {set name $replacement} set command {set name $replacement}
set command [subst $command] set command [subst $command]
eval $command ;# The same error as before: too many arguments to "set" in \ try {
{set name Archibald Sorbisol} eval $command ;# The same error as before: too many arguments to "set" in \
{set name Archibald Sorbisol}
} trap {TCL WRONGARGS} {result options} {
puts [list {received another error} $result]
}
# The proper way is to format the substituted value using use the "list" # "list" correctly formats a value for substitution:
# command.
set replacement [list {Archibald Sorbisol}] set replacement [list {Archibald Sorbisol}]
set command {set name $replacement} set command {set name $replacement}
set command [subst $command] set command [subst $command]
eval $command eval $command
# It is extremely common to see the "list" command being used to properly # "list" is commonly used to format values for substitution into scripts: There
# format values that are substituted into Tcl script templates. There are # are several examples of this, below.
# several examples of this, below.
# The "apply" command evaluates a string as a command. # "apply" evaluates a two-item list as a routine:
set cmd {{greeting name} { set cmd {{greeting name} {
return "$greeting, $name!" return "$greeting, $name!"
}} }}
apply $cmd Whaddup Neo apply $cmd Whaddup Neo
# A third item can be used to specify the namespace to apply the routine in:
set cmd [list {greeting name} {
return "$greeting, $name!"
} [namespace current]]
apply $cmd Whaddup Neo
# The "uplevel" command evaluates a script in some enclosing scope.
# "uplevel" evaluates a script at some higher level in the call stack:
proc greet {} { proc greet {} {
uplevel {puts "$greeting, $name"} uplevel {puts "$greeting, $name"}
} }
proc set_double {varname value} { proc set_double {varname value} {
if {[string is double $value]} { if {[string is double $value]} {
uplevel [list variable $varname $value] uplevel [list variable $varname $value]
} else { } else {
error [list {not a double} $value] error [list {not a double} $value]
} }
} }
# The "upvar" command links a variable in the current scope to a variable in # "upvar" links a variable at the current level in the call stack to a variable
# some enclosing scope # at some higher level:
proc set_double {varname value} { proc set_double {varname value} {
if {[string is double $value]} { if {[string is double $value]} {
upvar 1 $varname var upvar 1 $varname var
set var $value set var $value
} else { } else {
error [list {not a double} $value] error [list {not a double} $value]
} }
} }
# Get rid of the built-in "while" command. # Get rid of the built-in "while" routine, and use "proc" to define a new one:
rename ::while {} rename ::while {}
# handling is left as an exercise:
# Define a new while command with the "proc" command. More sophisticated error
# handling is left as an exercise.
proc while {condition script} { proc while {condition script} {
if {[uplevel 1 [list expr $condition]]} { if {[uplevel 1 [list expr $condition]]} {
uplevel 1 $script uplevel 1 $script
@ -425,27 +513,68 @@ proc while {condition script} {
} }
# The "coroutine" command creates a separate call stack, along with a command # "coroutine" creates a new call stack, a new routine to enter that call stack,
# to enter that call stack. The "yield" command suspends execution in that # and then calls that routine. "yield" suspends evaluation in that stack and
# stack. # returns control to the calling stack:
proc countdown {} { proc countdown count {
#send something back to the initial "coroutine" command # send something back to the creater of the coroutine, effectively pausing
yield # this call stack for the time being.
yield [info coroutine]
set count 3 while {$count > 1} {
while {$count > 1} { yield [incr count -1]
yield [incr count -1] }
} return 0
return 0
} }
coroutine countdown1 countdown coroutine countdown1 countdown 3
coroutine countdown2 countdown coroutine countdown2 countdown 5
puts [countdown 1] ;# -> 2 puts [countdown1] ;# -> 2
puts [countdown 2] ;# -> 2 puts [countdown2] ;# -> 4
puts [countdown 1] ;# -> 1 puts [countdown1] ;# -> 1
puts [countdown 1] ;# -> 0 puts [countdown1] ;# -> 0
puts [coundown 1] ;# -> invalid command name "countdown1" catch {
puts [countdown 2] ;# -> 1 puts [coundown1] ;# -> invalid command name "countdown1"
} cres copts
puts $cres
puts [countdown2] ;# -> 3
# Coroutine stacks can yield control to each other:
proc pass {whom args} {
return [yieldto $whom {*}$args]
}
coroutine a apply {{} {
yield
set result [pass b {please pass the salt}]
puts [list got the $result]
set result [pass b {please pass the pepper}]
puts [list got the $result]
}}
coroutine b apply {{} {
set request [yield]
while 1 {
set response [pass c $request]
puts [list [info coroutine] is now yielding]
set request [pass a $response]
}
}}
coroutine c apply {{} {
set request [yield]
while 1 {
if {[string match *salt* $request]} {
set request [pass b salt]
} else {
set request [pass b huh?]
}
}
}}
# get things moving
a
``` ```