mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-11-23 06:03:07 +03:00
590 lines
25 KiB
Markdown
590 lines
25 KiB
Markdown
|
---
|
|||
|
language: Tcl
|
|||
|
contributors:
|
|||
|
- ["Poor Yorick", "https://pooryorick.com/"]
|
|||
|
translators:
|
|||
|
- ["Viktor Sokhranov", "https://github.com/weirdvic"]
|
|||
|
filename: learntcl.tcl
|
|||
|
---
|
|||
|
|
|||
|
Tcl был создан [Джоном Оустерхаутом](https://ru.wikipedia.org/wiki/Оустерхаут,_Джон)
|
|||
|
в качестве скриптового языка в своих инструментах проектирования электрических цепей.
|
|||
|
В 1997 году за разработку языка Tcl автор получил [ACM](https://ru.wikipedia.org/wiki/ACM)
|
|||
|
Software System Award. Tcl может использоваться и как встраиваемый скриптовый язык,
|
|||
|
и как язык программирования общего назначения. Кроме того, он может быть использован как
|
|||
|
библиотека в программах на C, даже в случаях когда не требуется написание скриптов,
|
|||
|
поскольку Tcl может предоставить программе на C различные типы данных, такие как
|
|||
|
динамические строки, списки и хэш-таблицы. Также с помощью этой библиотеки возможно
|
|||
|
использовать форматирование строк, операции с файловой системой, работу с кодировками и
|
|||
|
динамически загружаемые библиотеки. К другим особенностям Tcl относятся:
|
|||
|
|
|||
|
* Удобный кроссплатформенный API для работы с сетью
|
|||
|
|
|||
|
* Поддержка виртуальной файловой системы(VFS)
|
|||
|
|
|||
|
* Стекируемые каналы ввода-вывода
|
|||
|
|
|||
|
* Асинхронность в ядре языка
|
|||
|
|
|||
|
* Поддержка корутин
|
|||
|
|
|||
|
* Простая и надёжная модель потоков выполнения
|
|||
|
|
|||
|
Tcl имеет много общего с Lisp, но в отличие от списков, в Tcl "валютой" языка
|
|||
|
являются строки. Все значения являются строками. Список в Tcl это просто строка в
|
|||
|
определённом формате, а тело процедуры(скрипт) это ещё одна строка, а не блок.
|
|||
|
С целью увеличения производительности, интерпретатор Tcl использует кэшированные
|
|||
|
внутренние представления различных типов данных. Например, рутины(routines), работающие
|
|||
|
со списками, фактически используют внутреннее представление списков, а интерпретатор
|
|||
|
Tcl обновляет строковое представление в том случае если оно используется в скрипте.
|
|||
|
В Tcl используется подход copy-on-write, позволяющий оперировать большими объёмами
|
|||
|
данных без дополнительного оверхеда. Процедуры в Tcl автоматически компилируются
|
|||
|
в байткод, кроме случаев когда в процедуре используются динамические рутины, такие
|
|||
|
как "uplevel", "upvar" и "trace"
|
|||
|
|
|||
|
Программировать на Tcl приятно. Его находят привлекательным хакеры, которым интересны
|
|||
|
Lisp, Forth или Smalltalk, а также инженеры и учёные, которым просто необходим
|
|||
|
гибкий инструмент для выполнения их задач. В Tcl языковые конструкции, включая
|
|||
|
циклы и математические операторы, представлены в виде изменяемых рутин, в отличие
|
|||
|
от других языков программирования, где они закреплены в синтаксисе, что позволяет
|
|||
|
синтаксису Tcl не мешать работать с предметной областью проекта. Синтаксис Tcl в этом
|
|||
|
смысле даже более минималистичен чем у Lisp.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```tcl
|
|||
|
#! /bin/env tclsh
|
|||
|
|
|||
|
###############################################################################
|
|||
|
## 1. Рекомендации
|
|||
|
###############################################################################
|
|||
|
|
|||
|
# Tcl это не shell или C! Этот момент требует уточнения, поскольку привычки
|
|||
|
# написания shell-скриптов почти работают в Tcl и часто люди начинают
|
|||
|
# изучать Tcl со знанием синтаксиса других языков. Поначалу это работает, но
|
|||
|
# когда скрипты становятся сложнее, наступает фрустрация.
|
|||
|
|
|||
|
# Фигурные скобки {} в Tcl используются не для построения блоков кода или
|
|||
|
# списков, а как механизм экранирования(quoting) для кода. Фактически в Tcl
|
|||
|
# нет ни списков, ни блоков кода. Фигурные скобки использутся для
|
|||
|
# экранирования специальных символов и потому подходят для представления
|
|||
|
# тела процедур и строк, которые должны интерпретироваться как списки.
|
|||
|
|
|||
|
|
|||
|
###############################################################################
|
|||
|
## 2. Синтаксис
|
|||
|
###############################################################################
|
|||
|
|
|||
|
# Скрипт состоит из команд, разделённых символами перевода строки или символами
|
|||
|
# точки с запятой. Каждая команда представляет собой вызов рутины. Первое слово
|
|||
|
# это имя вызываемой рутины, а последующие слова это аргументы. Слова разделены
|
|||
|
# пробелами. Так как каждый аргумент это слово в команде, он является строкой и
|
|||
|
# может быть неэкранирован:
|
|||
|
set part1 Sal
|
|||
|
set part2 ut; set part3 ations
|
|||
|
|
|||
|
|
|||
|
# символ доллара используется для подставления значения переменных:
|
|||
|
set greeting $part1$part2$part3
|
|||
|
|
|||
|
|
|||
|
# Когда "set" получает только имя переменной, возвращается значение переменной:
|
|||
|
set part3 ;# Возвращает значение переменной
|
|||
|
|
|||
|
|
|||
|
# Содержимое квадратных скобок заменяется на результат выполнения:
|
|||
|
set greeting $part1$part2[set part3]
|
|||
|
|
|||
|
|
|||
|
# Встроенный таким образов скрипт может состоять из нескольких команд, но
|
|||
|
# результат подстановки определяется последней командой:
|
|||
|
set greeting $greeting[
|
|||
|
incr i
|
|||
|
incr i
|
|||
|
incr i
|
|||
|
]
|
|||
|
puts $greeting ;# Выведет "Salutations3"
|
|||
|
|
|||
|
# Каждое слово в команде является строкой, включая имя рутины, поэтому
|
|||
|
# подстановки могут быть использованы и таким образом:
|
|||
|
set action pu
|
|||
|
|
|||
|
# следующие команды эквивалентны:
|
|||
|
puts $greeting
|
|||
|
${action}ts $greeting
|
|||
|
[set action]ts $greeting
|
|||
|
|
|||
|
|
|||
|
# Обратный слэш экранирует специальные символы:
|
|||
|
set amount \$16.42
|
|||
|
|
|||
|
|
|||
|
# и он же используется для ввода специальных символов:
|
|||
|
puts lots\nof\n\n\n\n\n\nnewlines
|
|||
|
|
|||
|
|
|||
|
# Слово в фигурных скобках никак не интерпретируется и в нём не работают
|
|||
|
# никакие подстановки, за исключением экранирования закрывающей скобки:
|
|||
|
set somevar {
|
|||
|
Это литерал знака $, а это \} экранированная закрывающая скобка
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# В слове внутри двойных кавычек, пробельные символы теряют своё
|
|||
|
# специальное значение:
|
|||
|
set name Neo
|
|||
|
set greeting "Hello, $name"
|
|||
|
|
|||
|
|
|||
|
# Имя переменной может быть любой строкой:
|
|||
|
set {first name} New
|
|||
|
|
|||
|
|
|||
|
# Фигурные скобки используются для доступа к переменным с составными именами:
|
|||
|
set greeting "Hello, ${first name}"
|
|||
|
|
|||
|
|
|||
|
# "set" всегда можно использовать вместо подстановки переменной:
|
|||
|
set greeting "Hello, [set {first name}]"
|
|||
|
|
|||
|
|
|||
|
# Чтобы "распаковать" список в команду используется оператор расширения "{*}"
|
|||
|
# Эти две команды эквивалентны:
|
|||
|
set name Neo
|
|||
|
set {*}{name Neo}
|
|||
|
|
|||
|
|
|||
|
# Массив это особая переменная, являющаяся контейнером для других переменных.
|
|||
|
set person(name) Neo
|
|||
|
set person(destiny) {The One}
|
|||
|
set greeting "Hello, $person(name)"
|
|||
|
|
|||
|
|
|||
|
# "variable" может быть использована для объявления или установки переменных.
|
|||
|
# В отличие от "set", которая использует глобальное и локальное пространство
|
|||
|
# имён, "variable" работает только с локальным пространством:
|
|||
|
variable name New
|
|||
|
|
|||
|
|
|||
|
# "namespace eval" создаёт новое пространство имён, если его не существует.
|
|||
|
# Пространство имён может содержать рутины и переменные:
|
|||
|
namespace eval people {
|
|||
|
namespace eval person1 {
|
|||
|
variable name Neo
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Двумя или более двоеточиями в именах переменных отделяется название
|
|||
|
# пространства имён:
|
|||
|
namespace eval people {
|
|||
|
set greeting "Hello $person1::name"
|
|||
|
}
|
|||
|
|
|||
|
# Два или более двоеточия также отделяют название пространства имён
|
|||
|
# в имени рутины:
|
|||
|
proc people::person1::speak {} {
|
|||
|
puts {I am The One.}
|
|||
|
}
|
|||
|
|
|||
|
# Полные(fully-qualified) имена начинаются с двух двоеточий:
|
|||
|
set greeting "Hello $::people::person1::name"
|
|||
|
|
|||
|
|
|||
|
|
|||
|
###############################################################################
|
|||
|
## 3. Больше никакого синтаксиса
|
|||
|
###############################################################################
|
|||
|
|
|||
|
# Все остальные функции реализованы посредством рутин. С этого момента и далее
|
|||
|
# больше нет нового синтаксиса. Всё остальное что можно изучить о Tcl это
|
|||
|
# поведение отдельных рутин и какие значения они присваивают своим аргументам.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
###############################################################################
|
|||
|
## 4. Переменные и пространства имён
|
|||
|
###############################################################################
|
|||
|
|
|||
|
# Каждая переменная и рутина связанс с пространством имён.
|
|||
|
|
|||
|
# Чтобы получить интерпретатор, которые не может сделать ничего, достаточно
|
|||
|
# удалить глобальное пространство имён. Особой пользы в этом нет, но это хорошо
|
|||
|
# иллюстрирует природу Tcl. Фактически имя глобального пространства имён это
|
|||
|
# пустая строка, но единственный способ представить её -- в виде полного имени:
|
|||
|
proc delete_global_namespace {} {
|
|||
|
namespace delete ::
|
|||
|
}
|
|||
|
|
|||
|
# Поскольку "set" всегда учитывает и глобальное, и текущее пространства имён,
|
|||
|
# более безопасно использовать "variable" чтобы объявить новую переменную или
|
|||
|
# задать значение переменной. Если переменная с именем "name" уже существует
|
|||
|
# в глобальном пространстве имён, использование "set" задаст значение
|
|||
|
# глобальной переменной, тогда как "variable" работает только с текущим
|
|||
|
# пространством имён.
|
|||
|
|
|||
|
namespace eval people {
|
|||
|
namespace eval person1 {
|
|||
|
variable name Neo
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# После объявления переменной в пространстве имён, [set] видит её, а не
|
|||
|
# одноимённую переменную в глобальном пространстве имён:
|
|||
|
|
|||
|
namespace eval people {
|
|||
|
namespace eval person1 {
|
|||
|
variable name
|
|||
|
set name Neo
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
# Но если "set" приходится создать новую переменную, он всегда делает это
|
|||
|
# с учётом текущего пространства имён:
|
|||
|
unset name
|
|||
|
namespace eval people {
|
|||
|
namespace eval person1 {
|
|||
|
set name neo
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
set people::person1::name
|
|||
|
|
|||
|
|
|||
|
# Абсолютное имя всегда начинается с имени глобального пространства имён, то
|
|||
|
# есть с пустой строки, за которой следует два двоеточия:
|
|||
|
set ::people::person1::name Neo
|
|||
|
|
|||
|
|
|||
|
# В пределах процедуры "variable" связывает перменную в текущем пространстве
|
|||
|
# имён с локальной областью видимости:
|
|||
|
namespace eval people::person1 {
|
|||
|
proc fly {} {
|
|||
|
variable name
|
|||
|
puts "$name is flying!"
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
###############################################################################
|
|||
|
## 5. Встроенные рутины
|
|||
|
###############################################################################
|
|||
|
|
|||
|
# Математические операции можно выполнять при помощи "expr":
|
|||
|
set a 3
|
|||
|
set b 4
|
|||
|
set c [expr {$a + $b}]
|
|||
|
|
|||
|
# Поскольку "expr" самостоятельно занимается подстановкой значений переменных,
|
|||
|
# математическое выражение нужно оборачивать в фигурные скобки чтобы отключить
|
|||
|
# подстановку значений переменных интерпретатором Tcl.
|
|||
|
# Подробнее об этом можно прочесть здесь:
|
|||
|
# "https://wiki.tcl-lang.org/page/Brace+your+expr-essions"
|
|||
|
|
|||
|
|
|||
|
# "expr" выполняет подстановку переменных и результатов команд:
|
|||
|
set c [expr {$a + [set b]}]
|
|||
|
|
|||
|
|
|||
|
# "expr" предоставляет разные математические функции:
|
|||
|
set c [expr {pow($a,$b)}]
|
|||
|
|
|||
|
|
|||
|
# Математические операторы сами по себе доступны в виде рутин в
|
|||
|
# пространстве имён ::tcl::mathop
|
|||
|
::tcl::mathop::+ 5 3
|
|||
|
|
|||
|
# Рутины могут быть импортированы из других пространств имён:
|
|||
|
namespace import ::tcl::mathop::+
|
|||
|
set result [+ 5 3]
|
|||
|
|
|||
|
|
|||
|
# Не числовые значения должны быть квотированы. Такие операторы как "eq"
|
|||
|
# Могут быть использованы чтобы провести строковое сравнение:
|
|||
|
set name Neo
|
|||
|
expr {{Bob} eq $name}
|
|||
|
|
|||
|
# Общие операторы сравнения тоже работают со строками если числовое значение
|
|||
|
# операнда недоступно:
|
|||
|
expr {{Bob} == $name}
|
|||
|
|
|||
|
|
|||
|
# "proc" создаёт новые рутины:
|
|||
|
proc greet name {
|
|||
|
return "Hello, $name!"
|
|||
|
}
|
|||
|
|
|||
|
# можно указать несколько параметров:
|
|||
|
proc greet {greeting name} {
|
|||
|
return "$greeting, $name!"
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Как было отмечено ранее, фигурные скобки не обозначают блок кода.
|
|||
|
# Любое значение, даже третий аргумент "proc" является строкой.
|
|||
|
# Предыдущая команда может быть переписана без использования фигурных скобок:
|
|||
|
|
|||
|
# As noted earlier, braces do not construct a code block. Every value, even
|
|||
|
# the third argument to "proc", is a string. The previous command
|
|||
|
# can be rewritten using no braces:
|
|||
|
proc greet greeting\ name return\ \"\$greeting,\ \$name!\"
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# Если последний параметр называется "args", все дополнительные аргументы,
|
|||
|
# переданные рутине, собираются в список и передаются как "args":
|
|||
|
proc fold {cmd first args} {
|
|||
|
foreach arg $args {
|
|||
|
set first [$cmd $first $arg]
|
|||
|
}
|
|||
|
return $first
|
|||
|
}
|
|||
|
fold ::tcl::mathop::* 5 3 3 ;# -> 45
|
|||
|
|
|||
|
|
|||
|
# Условное выполнение тоже реализовано как рутина:
|
|||
|
if {3 > 4} {
|
|||
|
puts {This will never happen}
|
|||
|
} elseif {4 > 4} {
|
|||
|
puts {This will also never happen}
|
|||
|
} else {
|
|||
|
puts {This will always happen}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Циклы реализованы как рутины. Первый и третий аргументы для "for"
|
|||
|
# обрабатываются как скрипты, а второй аргумент как выражение:
|
|||
|
set res 0
|
|||
|
for {set i 0} {$i < 10} {incr i} {
|
|||
|
set res [expr {$res + $i}]
|
|||
|
}
|
|||
|
unset res
|
|||
|
|
|||
|
|
|||
|
# Первый аргумент для "while" тоже обрабатывается как выражение:
|
|||
|
set i 0
|
|||
|
while {$i < 10} {
|
|||
|
incr i 2
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Список это строка, а элементы списка разделены пробелами:
|
|||
|
set amounts 10\ 33\ 18
|
|||
|
set amount [lindex $amounts 1]
|
|||
|
|
|||
|
# Если элемент списка содержит пробел, его надо экранировать:
|
|||
|
set inventory {"item 1" item\ 2 {item 3}}
|
|||
|
|
|||
|
|
|||
|
# Хорошая практика использовать списковые рутины для обработки списков:
|
|||
|
lappend inventory {item 1} {item 2} {item 3}
|
|||
|
|
|||
|
|
|||
|
# Фигурные скобки и бэкслеш могут быть использованы чтобы хранить более
|
|||
|
# комплексные структуры внутри списков. Список выглядит как скрипт, за
|
|||
|
# исключением того, что перевод строки и точка с запятой теряют своё
|
|||
|
# специальное значение, а также не производится подстановка значений.
|
|||
|
# Эта особенность Tcl называется гомоиконичность
|
|||
|
# https://ru.wikipedia.org/wiki/Гомоиконичность
|
|||
|
# В приведённом списке есть три элемента:
|
|||
|
set values {
|
|||
|
|
|||
|
one\ two
|
|||
|
|
|||
|
{three four}
|
|||
|
|
|||
|
five\{six
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Поскольку как и все значения, список является строкой, строковые
|
|||
|
# операции могут выполняться и над списком, с риском повреждения:
|
|||
|
set values {one two three four}
|
|||
|
set values [string map {two \{} $values] ;# $values больше не \
|
|||
|
правильно отформатированный список
|
|||
|
|
|||
|
|
|||
|
# Безопасный способ работать со списками — использовать "list" рутины:
|
|||
|
set values [list one \{ three four]
|
|||
|
lappend values { } ;# добавить символ пробела как элемент в список
|
|||
|
|
|||
|
|
|||
|
# Использование "eval" для вычисления значения скрипта:
|
|||
|
eval {
|
|||
|
set name Neo
|
|||
|
set greeting "Hello, $name"
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Список всегда можно передать в "eval" как скрипт, содержащий одну команду:
|
|||
|
eval {set name Neo}
|
|||
|
eval [list set greeting "Hello, $name"]
|
|||
|
|
|||
|
|
|||
|
# Следовательно, когда используется "eval", используйте "list" чтобы собрать
|
|||
|
# необходимую команду:
|
|||
|
set command {set name}
|
|||
|
lappend command {Archibald Sorbisol}
|
|||
|
eval $command
|
|||
|
|
|||
|
|
|||
|
# Частая ошибка: не использовать списковые функции для построения команды:
|
|||
|
set command {set name}
|
|||
|
append command { Archibald Sorbisol}
|
|||
|
try {
|
|||
|
eval $command ;# Здесь будет ошибка, превышено количество аргументов \
|
|||
|
к "set" в {set name Archibald Sorbisol}
|
|||
|
} on error {result eoptions} {
|
|||
|
puts [list {received an error} $result]
|
|||
|
}
|
|||
|
|
|||
|
# Эта ошибка запросто может произойти с "subst":
|
|||
|
|
|||
|
set replacement {Archibald Sorbisol}
|
|||
|
set command {set name $replacement}
|
|||
|
set command [subst $command]
|
|||
|
try {
|
|||
|
eval $command ;# Та же ошибка, лишние аргументы к \
|
|||
|
{set name Archibald Sorbisol}
|
|||
|
} trap {TCL WRONGARGS} {result options} {
|
|||
|
puts [list {received another error} $result]
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# "list" корректно форматирует значение для подстановки:
|
|||
|
set replacement [list {Archibald Sorbisol}]
|
|||
|
set command {set name $replacement}
|
|||
|
set command [subst $command]
|
|||
|
eval $command
|
|||
|
|
|||
|
|
|||
|
# "list" обычно используется для форматирования значений для подстановки в
|
|||
|
# скрипты, вот несколько примеров:
|
|||
|
|
|||
|
|
|||
|
# "apply" вычисляет список из двух элементов как рутину:
|
|||
|
set cmd {{greeting name} {
|
|||
|
return "$greeting, $name!"
|
|||
|
}}
|
|||
|
apply $cmd Whaddup Neo
|
|||
|
|
|||
|
# Третий элемент может быть использован для указания пространства имён рутины:
|
|||
|
set cmd [list {greeting name} {
|
|||
|
return "$greeting, $name!"
|
|||
|
} [namespace current]]
|
|||
|
apply $cmd Whaddup Neo
|
|||
|
|
|||
|
|
|||
|
# "uplevel" вычисляет скрипт на уровень выше в списке вызовов:
|
|||
|
proc greet {} {
|
|||
|
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]
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# "upvar" связывает переменную на текущем уровне вызовов с переменной на
|
|||
|
# более высоком уровне:
|
|||
|
proc set_double {varname value} {
|
|||
|
if {[string is double $value]} {
|
|||
|
upvar 1 $varname var
|
|||
|
set var $value
|
|||
|
} else {
|
|||
|
error [list {not a double} $value]
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# Избавляемся от встроенной рутины "while" и используем "proc" чтобы написать
|
|||
|
# свою версию:
|
|||
|
rename ::while {}
|
|||
|
# обработка оставлена как упражнение:
|
|||
|
proc while {condition script} {
|
|||
|
if {[uplevel 1 [list expr $condition]]} {
|
|||
|
uplevel 1 $script
|
|||
|
tailcall [namespace which while] $condition $script
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
# "coroutine" создаёт новый стек вызовов, новую рутину для входа в этот стек
|
|||
|
# и вызывает эту рутину. "yield" приостанавливает вычисления в этом стеке и
|
|||
|
# возвращает управление вызывавшему стеку:
|
|||
|
proc countdown count {
|
|||
|
# отправить что-нибудь обратно создателю корутины, фактически
|
|||
|
# останавливая стек вызовов на время.
|
|||
|
yield [info coroutine]
|
|||
|
|
|||
|
while {$count > 1} {
|
|||
|
yield [incr count -1]
|
|||
|
}
|
|||
|
return 0
|
|||
|
}
|
|||
|
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
|
|||
|
|
|||
|
|
|||
|
# Стеки корутин могут передавать контроль друг другу:
|
|||
|
|
|||
|
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?]
|
|||
|
}
|
|||
|
}
|
|||
|
}}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```
|
|||
|
|
|||
|
## Ссылки
|
|||
|
|
|||
|
[Официальная документация Tcl](https://www.tcl-lang.org)
|
|||
|
|
|||
|
[Tcl Wiki](https://wiki.tcl-lang.org)
|
|||
|
|
|||
|
[Tcl на Reddit](http://www.reddit.com/r/Tcl)
|