`

СПЕЦІАЛЬНІ
ПАРТНЕРИ
ПРОЕКТУ

Чи використовує ваша компанія ChatGPT в роботі?

BEST CIO

Определение наиболее профессиональных ИТ-управленцев, лидеров и экспертов в своих отраслях

Человек года

Кто внес наибольший вклад в развитие украинского ИТ-рынка.

Продукт года

Награды «Продукт года» еженедельника «Компьютерное обозрение» за наиболее выдающиеся ИТ-товары

 

Древняя, новая, будущая (продолжение)

0 
 

Где грамматика?

Достаточно подготовленный программист всегда начинает изучение любого нового языка программирования с хотя бы беглого просмотра формальной грамматики. Последней вполне хватало для быстрого знакомства с возможностями и различными нюансами некоторых особо изящно спроектированных языков. Казалось бы, описание Tcl как языка программирования целесообразнее всего было бы начинать в соответствии с традициями - ссылкой на формальную грамматику. Увы, поступить так не представляется возможным - Tcl не является языком, который можно описать статической грамматикой. Он относится к классу так называемых контекстно-зависимых языков, для которых традиционными (основанными на формах Бэкуса-Наура и их модификациях) грамматиками "распознавание" всех возможных формально правильных предложений принципиально неосуществимо. Донэл Феллоуз (Donal Fellows) об этой отличительной черте Tcl высказал следующее мнение: формально правильные Tcl-предложения не могут быть распознаны никаким иным вычислителем, кроме машины Тьюринга.

Так что выдумывать грамматику Tcl мы даже пытаться не будем. Равно как и не будем пугаться ее отсутствия - описание этого языка фактически является кратким сборником элементарных правил, перед обсуждением которых следует сразу сделать несколько важных отступлений.


Корни

Отсутствие грамматической формальности в Tcl с лихвой компенсируется богатством "исторического наследия ". От так и не ставшей массовой ОС Multics, а точнее, от командного языка этой легендарной системы Tcl унаследовал очень многие особенности - начиная от разделительных и группирующих символов и идей исполнения команд, заканчивая фундаментальными принципами реализации интерпретатора (например, динамическое связывание, автоматически инициирующее механизмы поиска кода, реализующего вызываемую команду). Из более поздней ОС Unix и ее инструментальных средств в Tcl перекочевали и идея двух видов группировки (с подстановками и без - из командных оболочек семейства Bourne shell), и регулярные выражения, и ассоциативные массивы. Колоссальное влияние на язык оказал другой великий инструмент - Lisp. В Tcl, как и в нем, основной структурой данных является список (это столь важное свойство, что ему мы посвятим отдельное отступление); списки реализованы динамически (т. е. длина списка может изменяться в ходе исполнения программы, а не фиксируется перед этапом интерпретации или компиляции); синтаксис обоих языков использует так называемую реверсивную польскую нотацию (РПН), при которой имя команды всегда следует первым (соответственно привычное арифметическое действие "5 + 3" в РПН записывается как "+ 5 3"), и даже рассмотренная ранее команда set возвращает результат записи в переменную или ее содержимое.


Тикль-дзен

Наверное, самое сложное в Tcl - это понимание: его простоты и в некотором роде сходности с естественными языками (упоминавшаяся ранее контекстная зависимость - неотъемлемое свойство естественных языков). Чтобы объяснить, о чем идет речь, придется воспользоваться следующим примером-задачкой...

Какой ответ на вопрос "что такое "рыба"? " вы бы выбрали из предложенных:
  1. живое существо, способное жить в воде;
  2. товар в магазине;
  3. игровая ситуация в домино;
  4. одна из пластмассовых игрушек вашего ребенка;
  5. не лучший сорт колбасы.
Правильным, как вы сами догадались, будет любой ответ - все зависит от контекста, в котором задается вопрос.

В контексте Tcl "вопрос про рыбу" может звучать так: какой из перечисленных типов данных является фундаментальным в Tcl:
  1. символы;
  2. строки;
  3. списки?
И, естественно, любой из трех вариантов ответа будет правильным. Потому как в Tcl все три типа данных, если так можно сказать, "фундаментально равноправны", и какой из них "правильнее " (или даже - уместнее) в каком- то фрагменте программы, зависит от точно определенного контекста.

Ярко выраженная контекстная зависимость - это только первая особенность в перечне отличительных черт Tcl. Вторая, не менее важная, заключена в самом названии языка и в "механике" функционирования системы, исполняющей написанные на нем программы. Давайте вспомним: Tcl - это аббревиатура Tool Command Language, язык управления инструментами. Причем в названии никак не определено, какие такие "инструменты " имеются в виду. Вот и самое время для еще одного "вопроса про рыбу": что означает слово "инструмент " (tool) в названии Tcl:
  1. встроенные реализации команд;
  2. дополнительные команды, создаваемые языковыми средствами Tcl;
  3. дополнительные команды, разрабатываемые на компилируемых языках и "встраиваемые" в Tcl;
  4. сторонние по отношению к Tcl исполняемые файлы различных утилит.
И, как в предыдущих примерах, любой ответ будет правильным. Естественно, встроенные команды языка - это основной инструментарий Tcl, но по сути все перечисленные инструменты практически равноправны - контекстом здесь является степень профессионализма того, кто их использует.


Самый главный алгоритм

Стандартная практика начального этапа изучения Tcl включает ознакомление со знаменитыми "11 принципами " этого языка, в которых на уровне правил определяется "самый главный алгоритм" - работа программы разбора (parser) интерпретатора Tcl. Изучить "11 принципов" каждый может самостоятельно, мы же попробуем взглянуть на эти принципы иначе...

Итак, Tcl как среду исполнения программ, написанных на одноименном языке, возможно представить как набор трех основных компонентов:
  • программы разбора (parser);
  • диспетчера команд-инструментов;
  • хранилища команд-инструментов.
Высокоуровневая логика работы системы очень проста. Программа разбора на основе ряда правил выделяет из входного потока символов "набор" вида "имя_команды параметр1 параметр2 ... параметрN" и передает его диспетчеру. Тот отыскивает в хранилище команд-инструментов соответствующий имени команды инструмент и активирует его, ретранслировав ему параметры. Вот, собственно, и вся высокоуровневая логика, теперь можно начать "погружение" в детали. Но перед "погружением" придется сделать небольшое отступление.

Из приведенной модели ясно, что, во-первых, знание принципов работы программы разбора является критически важным условием для понимания языка в целом, во-вторых, знание каждой команды-инструмента необходимо для эффективного использования системы. И наконец, менее очевидный вывод: высокоуровневая модель Tcl весьма далека от "традиционных" интерпретируемых языков и, по большому счету, похожа на вполне самостоятельную среду, концептуально близкую к комбинации "командный язык + утилитарный набор", например ОС Unix.

Пора "погружаться". Как и во всех машинных языках, программа разбора Tcl оперирует входным потоком символов, осуществляя преобразование последовательности символов исходного кода в: а во что, действительно? Если вспомнить примеры из "тикль-дзен", то "любой ответ будет правильным" - и во множество новых последовательностей символов, и во множество строк, и, наконец, в список. Причем последний вариант исключительно важен, и точка зрения на программу разбора Tcl как на "трансформатор" потока символов в списки во многих случаях позволяет избежать весьма малоприятных ошибок.

Процесс "погружения" мы начнем несколько неожиданно - с повторения аналогии, сформулированной Д. Феллоузом. Хотя бы потому, что программу разбора Tcl (в дальнейшем воспользуемся устоявшейся калькой с английского - парсер) удачнее всего можно представить в виде пусть сильно модифицированной, но все же: машины Тьюринга. Помните, что умеет делать эта машина? Да, собственно, почти ни- чего. Считывать находящийся под "кареткой " символ из текущей ячейки ленты, печатать символ в текущую ячейку, перемещать "каретку" на одну ячейку влево или вправо, на основании считанного символа переводить себя из "состояния " в "состояние". Никаких арифмети ческих операций, ничего лишнего. Практически то же самое делает и парсер Tcl на первом этапе разбора - этапе группировки, но эта "машина Тьюринга " имеет одно важное ограничение: она не умеет двигать "каретку" влево. "Только вправо" - вот первый "девиз" Tcl: исходный текст программы читается исключительно слева направо, и, соответственно, каждый символ в нем может быть пройден и обработан лишь один раз. Это еще одно очень важное правило Tcl, которое никогда нельзя забывать! Итак, парсер Tcl на этапе группировки считывает посимвольно программу слева направо и, как положено порядочной машине Тьюринга, на основании значения символа переводит ее в одно из внутренних состояний. Как и специальных символов (т. е. символов, активирующих новое состояние машины разбора Tcl), этих состояний немного. Первое важнейшее - "состояние группировки". В нем машина разбора формирует один из элементов того "набора ", который мы определили в описании высокоуровневой логики и который необходим диспетчеру для вызова соответствующего "инструмента". Особенность алгоритмики машины разбора заключается в том, что состояние группировки - всегда первое состояние, т. е. с него начинается каждый новый разбор очередного "набора". И из данного состояния машина разбора окончательно выходит только тогда, когда формирование "набора" полностью завершено - сигнализирует об этом непечатный символ новой строки или ";". Давайте сразу попробуем применить эти сведения при анализе простейшей совершенно законной "фразы" (или "ленты" в терминах машин Тьюринга) на Tcl, например такой: set A b. В начале разбора этой "ленты" машина находится в состоянии группировки. Она считывает слева направо последовательно символы "s", "e", "t", которые в силу своей "обычности" никак не влияют на состояние машины, и, наконец, доходит до символа пробела, указывающего ей, что группировка первого элемента "набора" закончена. Естественно, получилось слово "set". Теперь вспомним об упомянутой контекстной зависимости Tcl - общее понятие "состояния группировки", которым мы оперировали, придется немного уточнить, а именно - ввести несколько "подсостояний" в нем. То, что мы сейчас рассмотрели на примере, это группировка на основе только пробела-разделителя, самый простой вид группировки. Очевидно, что полу ченные в результате элементы "набора " не могут содержать пробелов. Для языков "эскизного программирования" (или "скриптового") это существенный недостаток. Преодолеть его позволяют две дополнительные разновидности группировки - на основе уточняющих разделителей: "пробел - двойные кавычки" и "пробел - открывающая фигурная скобка". Можете смело считать, что при их обработке, "натыкаясь" на символ "пробел", машина разбора переводится в состояние группировки, а следующий символ уточняет - в какую именно разновидность этого состояния (поэтому разделители и названы "уточняющими"). Особенность двух дополнительных подсостояний заключается в том, что из них машина может вернуться в примитивный режим группировки по разделителю-пробелу, исключительно обнаружив соответствующий специальный символ - или двойную кавычку, или закрывающую фигурную скобку. В этом состоянии машины разбора пробел теряет свою значимость специального символа. Не откладывая в долгий ящик, давайте сразу воспользуемся таким примером: set A "abc def". Что машина разбора сформирует в "наборе " группировкой на основе пробеларазделителя, очевидно - "set" и "A". Следующий же элемент набора группируется по-новому - и он будет равен . А вот и первый "фокус" Tcl - "лента", содержащая, допустим, такое: set A abc"def". Как отработает эту команду парсер Tcl, вы можете проверить прямо в консоли tkcon. Этот забавный пример показывает, что в состоянии группировки на основе пробела-разделителя машина разбора не придает "специальному" символу """ никакого особого значения (точно так же, как в состоянии группировки на основе уточняющего разделителя "двойная кавычка" игнорирует специальное значение пробелов) потому, что условие перевода в новое подсостояние не выполняется (не присутствует пара "пробел - уточняющий символ"). Аналогично осуществляется группировка на основе уточняющих разделителей "пробел - открывающая фигурная скобка". Проверьте в консоли такой пример: set B abc def. Раз выполнилась команда создания переменной с именем B и содержание переменной - "abc def", следовательно, парсер сформировал "набор" "set", "B", "abc def" и передал его диспетчеру, который отыскал реализацию "инструмента " с именем set и ретранслировал ему в качестве параметров "B" и "abc def". А теперь попробуйте еще один "фокус": set D abc"defghi. Заметьте, что здесь в значении переменной D использован весь "букет" "специальных символов " - и ни один из них не является в данном контексте специальным из-за того, что не выполнено условие перевода машины разбора в новое подсостояние. Давайте попробуем еще пару "фокусов ": set E "abc"конец строки - нажимаем Enter в консоли"def" и set F "abc ; def". Результат выполнения двух данных команд полностью соответствует принципам работы машины разбора: раз она попала в одно из подсостояний группировки с уточняющим разделителем, то выйти из него может, только встретив соответствующий символ - следовательно, "специальное значение" символов "конец строки" и ";" здесь утрачивается. И наконец, последний нюанс, позволяющий раскрыть уже использованный в первой части статьи пример. Помните, там мы воспользовались командой set Message "Hello, World\; I like Tcl!" и применили в ней символ подавления специального значения "\"? Но теперь-то нам известно, что ";" в том состоянии парсера, в которое он вошел после обнаружения первой пары символов "пробел - двойная кавычка", никакого специального значения не имеет. А вот символ "\" в результате выполнения команды куда-то пропал: Пожалуй, на данном этапе это единственная мелкая "неприятность" нашей модели "парсера Tcl почти по Тьюрингу". Но придется сделать одно уточнение: это "очень нехороший символ", поведение которого нужно просто выучить (а нехорошесть его заключается в том, что в каких-то нюансах он разрушает стройную аналогию с тьюринговской машиной, приоткрывая завесу над тем, насколько реальность отличается от идеалов). Впрочем, за исключением исклю чений (любимая программистская тавтология), "\" вполне порядочный символ, который, заставив парсер проигнорировать специальное значение следующего символа, просто отбрасывается. Вот пояснение в виде еще одного "фокуса": создайте некоторую переменную с любым значением, например set A 1+1 (а почему бы и нет - ничего о специальности символов "1" и "+" парсер даже не подозревает). А теперь попробуйте подавить специальное значение символа "пробел" в команде чтения содержимого переменной так: set\ A. Интерпретатор обзывается "инвалидом " - не важно. Важнее заметить, что "задавленный" пробел не позволил парсеру завершить и начать состояние группировки, и сформированный им "набор" состоит из одного элемента - , соответственно диспетчер не смог найти в наборе инструментов ничего с таким именем, и вот вам результат: "invalid command name "set A"".

Можете считать, с основными свойствами Tcl, родственными элементарной (и не совсем) машине Тьюринга, вы уже знакомы (что не исключает, естественно, ваших самостоятельных усилий в изучении ряда нюансов). Теперь наступает время усложнения модели.

Группировка - это, несомненно, сильное средство, но ее возможностей катастрофически не хватает для программирования. Поэтому в Tcl предусмотрена еще одна группа фундаментальных операций, выполняющихся на уровне парсера всегда после группировки - подстановки. Но до них мы доберемся позднее, пока же вспомним еще раз о "тикль-дзен"... О равноправности представлений "множество символов - строка - список" мы говорили и по ходу дела даже добавили к ним ни к чему не обязывающую аналогию с лентой машины Тьюринга. Собственно говоря, все это одно и то же. И много раз повторенный "набор" в таком случае мы можем представить кусками лент для машины Тьюринга - почему бы и нет? Значит, нам нужно только дополнить возможности элементарной машины Тьюринга способностью вырезать кусок ленты. Раз на этапе группировки машина разбора Tcl работает только один проход и передвигает "каретку " только слева направо, почему бы ей не "порезать" исходную ленту на соответствующие "куски" - их набор и будет упомянутым "набором". А поскольку у нас есть куски ленты для машины Тьюринга, значит, мы сохраняем целостность модели (точнее, пытаемся ее сохранить) и будем опять говорить об аналогичных машинах - "машинах подстановок".

Самая интересная, даже скажем концептуальная машина подстановки в Tcl, роднящая его с Lisp, - машина командной подстановки (МКП). В основу принципа работы МКП положена рекурсия, а "исполнительным механизмом " МКП является: весь интерпретатор Tcl. Объяснить это можно так: если в любом куске ленты, "нарезанной" на этапе группировки, встречается некоторая последовательность символов, заклю ченная между "[" и "]", МКП формирует из этой последовательности новую входную ленту и для ее обработки запускает всю "машину Tcl" - от группировки до выполнения "инструмента". Полученным за счет обработки результатом МКП заменяет исходную последовательность "[ : ]". Никаких синтети ческих ограничений на степень вложенности квадратных скобок в Tcl нет, поэтому при обработке ленты с текстом "..[ a1 [a2 [a3 ] ] ]:" вся машина будет рекурсивно вызываться три раза - из исполняющей основную ленту с новой входной лентой "a1 [ a2 [a3]]]", из нее - с входной лентой "a2 [a3]" и наконец, из нее - с входной лентой "a3". В общем, чистое функциональное программирование, которое при желании можно умудриться не замутнить никакими побочными эффектами.

Ready, set, buy! Посібник для початківців - як придбати Copilot для Microsoft 365

0 
 

Напечатать Отправить другу

Читайте также

 

Ukraine

 

  •  Home  •  Ринок  •  IТ-директор  •  CloudComputing  •  Hard  •  Soft  •  Мережі  •  Безпека  •  Наука  •  IoT