diff options
Diffstat (limited to 'tcl.html.markdown')
-rw-r--r--[-rwxr-xr-x] | tcl.html.markdown | 484 |
1 files changed, 312 insertions, 172 deletions
diff --git a/tcl.html.markdown b/tcl.html.markdown index f2d92fcd..40d9111a 100755..100644 --- a/tcl.html.markdown +++ b/tcl.html.markdown @@ -1,21 +1,21 @@ --- language: Tcl contributors: - - ["Poor Yorick", "http://pooryorick.com/"] + - ["Poor Yorick", "https://pooryorick.com/"] filename: learntcl.tcl --- -Tcl was created by [John Ousterhout](http://wiki.tcl.tk/John Ousterout) as a -reusable scripting language for chip design tools he was creating. In 1997 he +Tcl was created by [John Ousterhout](https://wiki.tcl.tk/John%20Ousterout) as a +reusable scripting language for circuit design tools that he authored. In 1997 he 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 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 such as dynamic strings, lists, and hash tables. The C library also provides portable functionality for loading dynamic libraries, string formatting and -code conversion, filesystem operations, network operations, and more. -Various features of Tcl stand out: +code conversion, filesystem operations, network operations, and more. Various +features of Tcl stand out: * Convenient cross-platform networking API @@ -30,183 +30,253 @@ Various features of Tcl stand out: * 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 -strings. A list is a string format. A procedure definition is a string -format. To achieve performance, Tcl internally caches structured -representations of these values. The list commands, for example, operate on +Tcl has much in common with Lisp, but instead of lists, Tcl uses strings as the +currency of the language. All values are strings. A list is a string with a +defined format, and the body of a procedure (a script) is also a string rather +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 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 -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". 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 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 -things like loops and mathematical operations that are usually baked into the +discipline of exposing all programmatic functionality as routines, including +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 -domain-specific functionality a project needs. It's syntax, which is even -lighter that that of Lisp, just gets out of the way. - - +domain-specific functionality a project needs. Its syntax, which is even +lighter than that of Lisp, just gets out of the way. ```tcl #! /bin/env tclsh -################################################################################ -## 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 # to get by with syntax they know from another language. It works at first, -# but soon leads to frustration with more complex scripts. - -# Braces are just a quoting mechanism, not a code block constructor or a list -# constructor. Tcl doesn't have either of those things. Braces are used, -# though, to escape special characters in procedure bodies and in strings that -# are formatted as lists. +# but soon leads to frustration when scripts become more complex. +# Braces are a quoting mechanism, not syntax for the construction of code +# blocks or lists. Tcl doesn't have either of those things. Braces are used to +# escape special characters, which makes them well-suited for quoting procedure +# bodies and strings that should be interpreted as lists. -################################################################################ -## 2. Syntax -################################################################################ -# Every line is a command. The first word is the name of the command, and -# subsequent words are arguments to the command. Words are delimited by -# whitespace. Since every word is a string, in the simple case no special -# markup such as quotes, braces, or backslash, is necessary. Even when quotes -# are used, they are not a string constructor, but just another escaping -# character. +############################################################################### +## 2. Syntax +############################################################################### -set greeting1 Sal -set greeting2 ut -set greeting3 ations +# A script is made up of commands delimited by newlines or semicolons. Each +# command is a call to a routine. The first word is the name of a routine to +# call, and subsequent words are arguments to the routine. Words are delimited +# by whitespace. Since each argument is a word in the command it is already a +# string, and may be unquoted: +set part1 Sal +set part2 ut; set part3 ations -#semicolon also delimits commands -set greeting1 Sal; set greeting2 ut; set greeting3 ations +# a dollar sign introduces variable substitution: +set greeting $part1$part2$part3 -# Dollar sign introduces variable substitution -set greeting $greeting1$greeting2$greeting3 +# When "set" is given only the name of a variable, it returns the +# value of that variable: +set part3 ;# Returns the value of the variable. -# Bracket introduces command substitution. The result of the command is -# substituted in place of the bracketed script. When the "set" command is -# given only the name of a variable, it returns the value of that variable. -set greeting $greeting1$greeting2[set greeting3] +# Left and right brackets embed a script to be evaluated for a result to +# substitute into the word: +set greeting $part1$part2[set part3] -# Command substitution should really be called script substitution, because an -# entire script, not just a command, can be placed between the brackets. The -# "incr" command increments the value of a variable and returns its value. +# An embedded script may be composed of multiple commands, the last of which provides +# the result for the substitution: set greeting $greeting[ - incr i - incr i - incr i + incr i + incr i + incr i ] +puts $greeting ;# The output is "Salutations3" + +# 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 + +# backslash suppresses the special meaning of characters: 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 # 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 look#ing for the closing brace +# substitutions, except that a backslash before a brace is not counted when +# looking for the closing brace: set somevar { This is a literal $ sign, and this \} escaped brace remains uninterpreted -} +} # In a word enclosed in double quotes, whitespace characters lose their special -# meaning +# meaning: set name Neo set greeting "Hello, $name" -#variable names can be any string +# A variable name can be any string: 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}" -# 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}]" -# To promote the words within a word to individual words of the current -# command, use the expansion operator, "{*}". -set {*}{name Neo} - -# is equivalent to +# To unpack a list into the command, use the expansion operator, "{*}". These +# two commands are equivalent: set name Neo +set {*}{name Neo} # An array is a special variable that is a container for other variables. set person(name) Neo -set person(gender) male +set person(destiny) {The One} 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 person1 { - set name Neo + variable name Neo } } -#The full name of a variable includes its enclosing namespace(s), delimited by two colons: -set greeting "Hello $people::person::name" +# Use two or more colons to delimit namespace components in variable names: +namespace eval people { + 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. No More Syntax +############################################################################### +# 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 the behaviour of individual routines and what meaning they +# assign to their arguments. -################################################################################ -## 3. A Few Notes -################################################################################ -# All other functionality is implemented via commands. From this point on, -# there is no new syntax. Everything else there is to learn about Tcl is about -# the behaviour of individual commands, and what meaning they 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 # namespace. It's not very useful to do such a thing, but it illustrates the -# nature of Tcl. -namespace delete :: - +# nature of Tcl. The name of the global namespace is actually the empty +# 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 of name resolution behaviour, its safer to use the "variable" command to declare or to assign a value to a namespace. +# Because "set" always keeps its eye on both the global namespace and the +# current namespace, it's safer to use "variable" to declare a variable or +# assign a value to a variable. If a variable called "name" already exists in +# the global namespace, using "set" here will assign a value to the global +# variable instead of to a variable in the current namespace, whereas +# "variable" operates only on the current namespace. namespace eval people { namespace eval person1 { 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 + } -# The full name of a variable can always be used, if desired. -set people::person1::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 -################################################################################ -## 4. Commands -################################################################################ -# Math can be done with the "expr" command. +# 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!" + } +} + + + + +############################################################################### +## 4. Built-in Routines +############################################################################### + +# Math can be done with the "expr": set a 3 set b 4 set c [expr {$a + $b}] @@ -216,53 +286,63 @@ set c [expr {$a + $b}] # "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]}] -# The "expr" command provides a set of mathematical functions +# "expr" provides a set of mathematical functions: set c [expr {pow($a,$b)}] -# Mathematical operators are available as commands in the ::tcl::mathop -# namespace +# Mathematical operators are available as routines in the ::tcl::mathop +# namespace: ::tcl::mathop::+ 5 3 -# Commands can be imported from other namespaces +# Routines can be imported from other namespaces: namespace import ::tcl::mathop::+ 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 { return "Hello, $name!" } -#multiple parameters can be specified +#multiple parameters can be specified: proc greet {greeting name} { return "$greeting, $name!" } # 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 -# rewritten to not use braces at all: -proc greet greeting\ name return\ \"Hello,\ \$name! +# the third argument to "proc", is a string. The previous command +# can be rewritten using no braces: +proc greet greeting\ name return\ \"\$greeting,\ \$name!\" -# When the last parameter is the literal value, "args", it collects all extra -# arguments when the command is invoked -proc fold {cmd args} { - set res 0 +# When the last parameter is the literal value "args", all extra arguments +# passed to the routine are collected into a list and assigned to "args": +proc fold {cmd first args} { foreach arg $args { - set res [cmd $res $arg] + set first [$cmd $first $arg] } + return $first } fold ::tcl::mathop::* 5 3 3 ;# -> 45 -# Conditional execution is implemented as a command +# Conditional execution is implemented as a routine: if {3 > 4} { puts {This will never happen} } elseif {4 > 4} { @@ -272,30 +352,40 @@ if {3 > 4} { } -# Loops are implemented as commands. The first, second, and third -# arguments of the "for" command are treated as mathematical expressions +# Loops are implemented as routines. The first and third arguments to +# "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} { set res [expr {$res + $i}] } +unset res -# The first argument of the "while" command is also treated as a mathematical -# expression +# The first argument to "while" is also treated as an expression: set i 0 while {$i < 10} { incr i 2 } -# A list is a specially-formatted string. In the simple case, whitespace is sufficient to delimit values -set amounts 10\ 33\ 18 +# A list is a string, and items in the list are delimited by whitespace: +set amounts 10\ 33\ 18 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 modifying lists: +lappend inventory {item 1} {item 2} {item 3} + # 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 -# semicolon character lose their special meanings. This feature makes Tcl -# homoiconic. There are three items in the following list. +# semicolon character lose their special meanings, and there is no script or +# variable substitution. This feature makes Tcl homoiconic. There are three +# items in the following list: set values { one\ two @@ -307,19 +397,19 @@ set values { } -# Since a list is a string, string operations could be performed on it, at the -# risk of corrupting the formatting of the list. +# Since, like all values, a list is a string, string operations could be +# performed on it, at the risk of corrupting the formatting of the list: set values {one two three four} 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] 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 { set name Neo set greeting "Hello, $name" @@ -327,84 +417,93 @@ eval { # A list can always be passed to "eval" as a script composed of a single -# command. +# command: eval {set name Neo} 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} lappend command {Archibald Sorbisol} 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} append command { Archibald Sorbisol} -eval $command ;# There is an error here, because there are too many arguments \ - to "set" in {set name Archibald Sorbisol} +try { + 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 command {set name $replacement} set command [subst $command] -eval $command ;# The same error as before: to many arguments to "set" in \ - {set name Archibald Sorbisol} +try { + 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" -# command. +# "list" correctly formats a value for substitution: set replacement [list {Archibald Sorbisol}] set command {set name $replacement} -set command [subst $command] +set command [subst $command] eval $command -# It is extremely common to see the "list" command being used to properly -# format values that are substituted into Tcl script templates. There are -# several examples of this, below. +# "list" is commonly used to format values for substitution into scripts: There +# are 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} { return "$greeting, $name!" }} 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 {} { - uplevel {puts "$greeting, $name"} + uplevel {puts "$greeting, $name"} } proc set_double {varname value} { - if {[string is double $value]} { - uplevel [list variable $varname $value] - } else { - error [list {not a double} $value] - } + if {[string is double $value]} { + uplevel [list variable $varname $value] + } else { + error [list {not a double} $value] + } } -# The "upvar" command links a variable in the current scope to a variable in -# some enclosing scope +# "upvar" links a variable at the current level in the call stack to a variable +# at some higher level: proc set_double {varname value} { - if {[string is double $value]} { - upvar 1 $varname var - set var $value - } else { - error [list {not a double} $value] - } + if {[string is double $value]} { + upvar 1 $varname var + set var $value + } else { + 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 {} - - -# Define a new while command with the "proc" command. More sophisticated error -# handling is left as an exercise. +# handling is left as an exercise: proc while {condition script} { if {[uplevel 1 [list expr $condition]]} { uplevel 1 $script @@ -413,27 +512,68 @@ proc while {condition script} { } -# The "coroutine" command creates a separate call stack, along with a command -# to enter that call stack. The "yield" command suspends execution in that -# stack. -proc countdown {} { - #send something back to the initial "coroutine" command - yield +# "coroutine" creates a new call stack, a new routine to enter that call stack, +# and then calls that routine. "yield" suspends evaluation in that stack and +# returns control to the calling stack: +proc countdown count { + # send something back to the creator of the coroutine, effectively pausing + # this call stack for the time being. + yield [info coroutine] - set count 3 - while {$count > 1} { - yield [incr count -1] - } - return 0 + while {$count > 1} { + yield [incr count -1] + } + return 0 } -coroutine countdown1 countdown -coroutine countdown2 countdown -puts [countdown 1] ;# -> 2 -puts [countdown 2] ;# -> 2 -puts [countdown 1] ;# -> 1 -puts [countdown 1] ;# -> 0 -puts [coundown 1] ;# -> invalid command name "countdown1" -puts [countdown 2] ;# -> 1 +coroutine countdown1 countdown 3 +coroutine countdown2 countdown 5 +puts [countdown1] ;# -> 2 +puts [countdown2] ;# -> 4 +puts [countdown1] ;# -> 1 +puts [countdown1] ;# -> 0 +catch { + 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 ``` |