1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
|
---
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)
|