tcl.md

Личный сайт Go-разработчика из Казани

Tcl был создан Джоном Оустерхаутом в качестве скриптового языка в своих инструментах проектирования электрических цепей. В 1997 году за разработку языка Tcl автор получил 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.

1#! /bin/env tclsh 2 3############################################################################### 4## 1. Рекомендации 5############################################################################### 6 7# Tcl это не shell или C! Этот момент требует уточнения, поскольку привычки 8# написания shell-скриптов почти работают в Tcl и часто люди начинают 9# изучать Tcl со знанием синтаксиса других языков. Поначалу это работает, но 10# когда скрипты становятся сложнее, наступает фрустрация. 11 12# Фигурные скобки {} в Tcl используются не для построения блоков кода или 13# списков, а как механизм экранирования (quoting) для кода. Фактически в Tcl 14# нет ни списков, ни блоков кода. Фигурные скобки использутся для 15# экранирования специальных символов и потому подходят для представления 16# тела процедур и строк, которые должны интерпретироваться как списки. 17 18 19############################################################################### 20## 2. Синтаксис 21############################################################################### 22 23# Скрипт состоит из команд, разделённых символами перевода строки или символами 24# точки с запятой. Каждая команда представляет собой вызов рутины. Первое слово 25# это имя вызываемой рутины, а последующие слова это аргументы. Слова разделены 26# пробелами. Так как каждый аргумент это слово в команде, он является строкой и 27# может быть неэкранирован: 28set part1 Sal 29set part2 ut; set part3 ations 30 31 32# символ доллара используется для подставления значения переменных: 33set greeting $part1$part2$part3 34 35 36# Когда "set" получает только имя переменной, возвращается значение переменной: 37set part3 ;# Возвращает значение переменной 38 39 40# Содержимое квадратных скобок заменяется на результат выполнения: 41set greeting $part1$part2[set part3] 42 43 44# Встроенный таким образов скрипт может состоять из нескольких команд, но 45# результат подстановки определяется последней командой: 46set greeting $greeting[ 47 incr i 48 incr i 49 incr i 50] 51puts $greeting ;# Выведет "Salutations3" 52 53# Каждое слово в команде является строкой, включая имя рутины, поэтому 54# подстановки могут быть использованы и таким образом: 55set action pu 56 57# следующие команды эквивалентны: 58puts $greeting 59${action}ts $greeting 60[set action]ts $greeting 61 62 63# Обратный слэш экранирует специальные символы: 64set amount \$16.42 65 66 67# и он же используется для ввода специальных символов: 68puts lots\nof\n\n\n\n\n\nnewlines 69 70 71# Слово в фигурных скобках никак не интерпретируется и в нём не работают 72# никакие подстановки, за исключением экранирования закрывающей скобки: 73set somevar { 74 Это литерал знака $, а это \} экранированная закрывающая скобка 75} 76 77 78# В слове внутри двойных кавычек, пробельные символы теряют своё 79# специальное значение: 80set name Neo 81set greeting "Hello, $name" 82 83 84# Имя переменной может быть любой строкой: 85set {first name} New 86 87 88# Фигурные скобки используются для доступа к переменным с составными именами: 89set greeting "Hello, ${first name}" 90 91 92# "set" всегда можно использовать вместо подстановки переменной: 93set greeting "Hello, [set {first name}]" 94 95 96# Чтобы "распаковать" список в команду используется оператор расширения "{*}" 97# Эти две команды эквивалентны: 98set name Neo 99set {*}{name Neo} 100 101 102# Массив это особая переменная, являющаяся контейнером для других переменных. 103set person(name) Neo 104set person(destiny) {The One} 105set greeting "Hello, $person(name)" 106 107 108# "variable" может быть использована для объявления или установки переменных. 109# В отличие от "set", которая использует глобальное и локальное пространство 110# имён, "variable" работает только с локальным пространством: 111variable name New 112 113 114# "namespace eval" создаёт новое пространство имён, если его не существует. 115# Пространство имён может содержать рутины и переменные: 116namespace eval people { 117 namespace eval person1 { 118 variable name Neo 119 } 120} 121 122 123# Двумя или более двоеточиями в именах переменных отделяется название 124# пространства имён: 125namespace eval people { 126 set greeting "Hello $person1::name" 127} 128 129# Два или более двоеточия также отделяют название пространства имён 130# в имени рутины: 131proc people::person1::speak {} { 132 puts {I am The One.} 133} 134 135# Полные(fully-qualified) имена начинаются с двух двоеточий: 136set greeting "Hello $::people::person1::name" 137 138 139 140############################################################################### 141## 3. Больше никакого синтаксиса 142############################################################################### 143 144# Все остальные функции реализованы посредством рутин. С этого момента и далее 145# больше нет нового синтаксиса. Всё остальное что можно изучить о Tcl это 146# поведение отдельных рутин и какие значения они присваивают своим аргументам. 147 148 149 150############################################################################### 151## 4. Переменные и пространства имён 152############################################################################### 153 154# Каждая переменная и рутина связана с пространством имён. 155 156# Чтобы получить интерпретатор, который не может сделать ничего, достаточно 157# удалить глобальное пространство имён. Особой пользы в этом нет, но это хорошо 158# иллюстрирует природу Tcl. Фактически имя глобального пространства имён это 159# пустая строка, но единственный способ представить её -- в виде полного имени: 160proc delete_global_namespace {} { 161 namespace delete :: 162} 163 164# Поскольку "set" всегда учитывает и глобальное, и текущее пространства имён, 165# более безопасно использовать "variable" чтобы объявить новую переменную или 166# задать значение переменной. Если переменная с именем "name" уже существует 167# в глобальном пространстве имён, использование "set" задаст значение 168# глобальной переменной, тогда как "variable" работает только с текущим 169# пространством имён. 170 171namespace eval people { 172 namespace eval person1 { 173 variable name Neo 174 } 175} 176 177# После объявления переменной в пространстве имён, [set] видит её, а не 178# одноимённую переменную в глобальном пространстве имён: 179 180namespace eval people { 181 namespace eval person1 { 182 variable name 183 set name Neo 184 } 185} 186 187# Но если "set" приходится создать новую переменную, он всегда делает это 188# с учётом текущего пространства имён: 189unset name 190namespace eval people { 191 namespace eval person1 { 192 set name neo 193 } 194 195} 196set people::person1::name 197 198 199# Абсолютное имя всегда начинается с имени глобального пространства имён, то 200# есть с пустой строки, за которой следует два двоеточия: 201set ::people::person1::name Neo 202 203 204# В пределах процедуры "variable" связывает перменную в текущем пространстве 205# имён с локальной областью видимости: 206namespace eval people::person1 { 207 proc fly {} { 208 variable name 209 puts "$name is flying!" 210 } 211} 212 213 214 215 216############################################################################### 217## 5. Встроенные рутины 218############################################################################### 219 220# Математические операции можно выполнять при помощи "expr": 221set a 3 222set b 4 223set c [expr {$a + $b}] 224 225# Поскольку "expr" самостоятельно занимается подстановкой значений переменных, 226# математическое выражение нужно оборачивать в фигурные скобки чтобы отключить 227# подстановку значений переменных интерпретатором Tcl. 228# Подробнее об этом можно прочесть здесь: 229# "https://wiki.tcl-lang.org/page/Brace+your+expr-essions" 230 231 232# "expr" выполняет подстановку переменных и результатов команд: 233set c [expr {$a + [set b]}] 234 235 236# "expr" предоставляет разные математические функции: 237set c [expr {pow($a,$b)}] 238 239 240# Математические операторы сами по себе доступны в виде рутин в 241# пространстве имён ::tcl::mathop 242::tcl::mathop::+ 5 3 243 244# Рутины могут быть импортированы из других пространств имён: 245namespace import ::tcl::mathop::+ 246set result [+ 5 3] 247 248 249# Не числовые значения должны быть квотированы. Такие операторы как "eq" 250# Могут быть использованы чтобы провести строковое сравнение: 251set name Neo 252expr {{Bob} eq $name} 253 254# Общие операторы сравнения тоже работают со строками если числовое значение 255# операнда недоступно: 256expr {{Bob} == $name} 257 258 259# "proc" создаёт новые рутины: 260proc greet name { 261 return "Hello, $name!" 262} 263 264# можно указать несколько параметров: 265proc greet {greeting name} { 266 return "$greeting, $name!" 267} 268 269 270# Как было отмечено ранее, фигурные скобки не обозначают блок кода. 271# Любое значение, даже третий аргумент "proc", является строкой. 272# Предыдущая команда может быть переписана без использования фигурных скобок: 273proc greet greeting\ name return\ \"\$greeting,\ \$name!\" 274 275 276 277# Если последний параметр называется "args", все дополнительные аргументы, 278# переданные рутине, собираются в список и передаются как "args": 279proc fold {cmd first args} { 280 foreach arg $args { 281 set first [$cmd $first $arg] 282 } 283 return $first 284} 285fold ::tcl::mathop::* 5 3 3 ;# -> 45 286 287 288# Условное выполнение тоже реализовано как рутина: 289if {3 > 4} { 290 puts {This will never happen} 291} elseif {4 > 4} { 292 puts {This will also never happen} 293} else { 294 puts {This will always happen} 295} 296 297 298# Циклы реализованы как рутины. Первый и третий аргумент для "for" 299# обрабатываются как скрипты, а второй аргумент как выражение: 300set res 0 301for {set i 0} {$i < 10} {incr i} { 302 set res [expr {$res + $i}] 303} 304unset res 305 306 307# Первый аргумент для "while" тоже обрабатывается как выражение: 308set i 0 309while {$i < 10} { 310 incr i 2 311} 312 313 314# Список это строка, а элементы списка разделены пробелами: 315set amounts 10\ 33\ 18 316set amount [lindex $amounts 1] 317 318# Если элемент списка содержит пробел, его надо экранировать: 319set inventory {"item 1" item\ 2 {item 3}} 320 321 322# Хорошая практика использовать списковые рутины для обработки списков: 323lappend inventory {item 1} {item 2} {item 3} 324 325 326# Фигурные скобки и бэкслеш могут быть использованы чтобы хранить более 327# комплексные структуры внутри списков. Список выглядит как скрипт, за 328# исключением того, что перевод строки и точка с запятой теряют своё 329# специальное значение, а также не производится подстановка значений. 330# Эта особенность Tcl называется гомоиконичность 331# https://ru.wikipedia.org/wiki/Гомоиконичность 332# В приведённом списке есть три элемента: 333set values { 334 335 one\ two 336 337 {three four} 338 339 five\{six 340 341} 342 343 344# Поскольку как и все значения, список является строкой, строковые 345# операции могут выполняться и над списком, с риском повреждения: 346set values {one two three four} 347set values [string map {two \{} $values] ;# $values больше не \ 348 правильно отформатированный список 349 350 351# Безопасный способ работать со списками — использовать "list" рутины: 352set values [list one \{ three four] 353lappend values { } ;# добавить символ пробела как элемент в список 354 355 356# Использование "eval" для вычисления значения скрипта: 357eval { 358 set name Neo 359 set greeting "Hello, $name" 360} 361 362 363# Список всегда можно передать в "eval" как скрипт, содержащий одну команду: 364eval {set name Neo} 365eval [list set greeting "Hello, $name"] 366 367 368# Следовательно, когда используется "eval", используйте "list" чтобы собрать 369# необходимую команду: 370set command {set name} 371lappend command {Archibald Sorbisol} 372eval $command 373 374 375# Частая ошибка: не использовать списковые функции для построения команды: 376set command {set name} 377append command { Archibald Sorbisol} 378try { 379 eval $command ;# Здесь будет ошибка, превышено количество аргументов \ 380 к "set" в {set name Archibald Sorbisol} 381} on error {result eoptions} { 382 puts [list {received an error} $result] 383} 384 385# Эта ошибка запросто может произойти с "subst": 386 387set replacement {Archibald Sorbisol} 388set command {set name $replacement} 389set command [subst $command] 390try { 391 eval $command ;# Та же ошибка, лишние аргументы к \ 392 {set name Archibald Sorbisol} 393} trap {TCL WRONGARGS} {result options} { 394 puts [list {received another error} $result] 395} 396 397 398# "list" корректно форматирует значение для подстановки: 399set replacement [list {Archibald Sorbisol}] 400set command {set name $replacement} 401set command [subst $command] 402eval $command 403 404 405# "list" обычно используется для форматирования значений для подстановки в 406# скрипты, вот несколько примеров: 407 408 409# "apply" вычисляет список из двух элементов как рутину: 410set cmd {{greeting name} { 411 return "$greeting, $name!" 412}} 413apply $cmd Whaddup Neo 414 415# Третий элемент может быть использован для указания пространства имён рутины: 416set cmd [list {greeting name} { 417 return "$greeting, $name!" 418} [namespace current]] 419apply $cmd Whaddup Neo 420 421 422# "uplevel" вычисляет скрипт на уровень выше в списке вызовов: 423proc greet {} { 424 uplevel {puts "$greeting, $name"} 425} 426 427proc set_double {varname value} { 428 if {[string is double $value]} { 429 uplevel [list variable $varname $value] 430 } else { 431 error [list {not a double} $value] 432 } 433} 434 435 436# "upvar" связывает переменную на текущем уровне вызовов с переменной на 437# более высоком уровне: 438proc set_double {varname value} { 439 if {[string is double $value]} { 440 upvar 1 $varname var 441 set var $value 442 } else { 443 error [list {not a double} $value] 444 } 445} 446 447 448# Избавляемся от встроенной рутины "while" и используем "proc" чтобы написать 449# свою версию: 450rename ::while {} 451# обработка оставлена как упражнение: 452proc while {condition script} { 453 if {[uplevel 1 [list expr $condition]]} { 454 uplevel 1 $script 455 tailcall [namespace which while] $condition $script 456 } 457} 458 459 460# "coroutine" создаёт новый стек вызовов, новую рутину для входа в этот стек 461# и вызывает эту рутину. "yield" приостанавливает вычисления в этом стеке и 462# возвращает управление вызывавшему стеку: 463proc countdown count { 464 # отправить что-нибудь обратно создателю корутины, фактически 465 # останавливая стек вызовов на время. 466 yield [info coroutine] 467 468 while {$count > 1} { 469 yield [incr count -1] 470 } 471 return 0 472} 473coroutine countdown1 countdown 3 474coroutine countdown2 countdown 5 475puts [countdown1] ;# -> 2 476puts [countdown2] ;# -> 4 477puts [countdown1] ;# -> 1 478puts [countdown1] ;# -> 0 479catch { 480 puts [coundown1] ;# -> invalid command name "countdown1" 481} cres copts 482puts $cres 483puts [countdown2] ;# -> 3 484 485 486# Стеки корутин могут передавать контроль друг другу: 487 488proc pass {whom args} { 489 return [yieldto $whom {*}$args] 490} 491 492coroutine a apply {{} { 493 yield 494 set result [pass b {please pass the salt}] 495 puts [list got the $result] 496 set result [pass b {please pass the pepper}] 497 puts [list got the $result] 498}} 499 500coroutine b apply {{} { 501 set request [yield] 502 while 1 { 503 set response [pass c $request] 504 puts [list [info coroutine] is now yielding] 505 set request [pass a $response] 506 } 507}} 508 509coroutine c apply {{} { 510 set request [yield] 511 while 1 { 512 if {[string match *salt* $request]} { 513 set request [pass b salt] 514 } else { 515 set request [pass b huh?] 516 } 517 } 518}}

Ссылки

Официальная документация Tcl

Tcl Wiki

Tcl на Reddit