"Иной" скриптинг. Часть вторая

13 ноябрь, 2002 - 00:00Андрей Зубинский
Использование ярких контрастов -- излюбленный прием литераторов. В нашем случае, к сожалению, не до такой утонченности, и все же... От экзотичного, мощного и по-своему уникального языка Rebol мы перейдем к ознакомлению с разработкой не менее полезной... и несоизмеримо менее популярной. Если сравнительно "молодой" Rebol уже сформировал внушительное количество тематических Internet-ресурсов, ему посвящают статьи ведущие издания для разработчиков и к нему серьезно "присматриваются" Apple и IBM, то предмет нашего сегодняшнего обсуждения, хорошо известный в научном сообществе, популярным назвать трудно. Предваряя необходимые отступления, стоит предупредить читателя сразу -- в этой статье не будет "потешных" примеров и даже попытки "облегчить участь" начинающим "с нуля". В данном случае такой подход просто непозволителен, и далее будет понятно, почему.

Если прибегнуть к помощи какого-нибудь хорошего сетевого ресурса и попытаться даже не проанализировать (анализ -- слишком серьезная задача), а просто оценить все разнообразие интерпретируемых языков программирования, в той или иной степени соответствующих неформальному сленговому определению "скриптовых", можно сделать ряд очевидных "открытий". Например, взглянув на страницу портала dmoz, посвященную интерпретируемым языкам, можно вполне уверенно говорить о том, что в мире скриптинга правят бал весьма специфические разработки, максимально удаленные от "традиционных", ориентированных на быстрое исполнение компилируемых языков. И этот факт, имеющий столь очевидное объяснение, что мы его даже не упомянем, тем более подчеркивает специфичность героя сегодняшнего обсуждения. Все дело здесь заключается в весьма неприятном парадоксе, которому стоит уделить минуту внимания.

Традиционно и небезосновательно принято считать, что основные инструментально-языковые средства современного массового программирования (естественно, это знаменитые языки C и C++) относятся к классу потенциально опасных и обладающих массой "врожденных" недостатков. Между тем большинство претендующих на "совершенство" средств аналогичного назначения, опять же, создаются именно с помощью такого опасного инструментария. В частности, среди обширного класса интерпретаторов можно найти один-два образчика, нарушающих это правило, но основная масса ему полностью соответствует, сколь бы ни был велик разрыв между уровнем двух инструментов -- новосозданного и использованного в процессе создания.

"Иной" скриптинг. Часть вторая
Минимальный инструментальный набор, необходимый для эффективного использования CINT, вне зависимости от платформы более чем скромен -- текстовый редактор и консоль

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

Теперь мы готовы сформулировать минимальный перечень требований к скриптовому языку "для программистов": он должен быть максимально приближенным к используемым в традиционном процессе разработки массово-популярным языковым средствам и интерпретируемым (что существенно облегчает решение задач прототипирования и отладки). Наконец, он должен быть доступным, мобильным и стабильно развивающимся. Это, скажем так, программа-минимум. А уж если такой инструмент будет обладать врожденными способностями к "встраиванию" в приложения и расширяемости, можно говорить и о выполнении программы-максимум, которую ставят перед собой далеко не все создатели языковых средств.

Собственно, на этом отступления завершаются, и наступает время непосредственного знакомства с по-своему уникальным скриптовым языком, который, по крайней мере, не добавляет головной боли...


Аспект второй -- прототипирование

Looks pretty inconsistent,but this is C++.
Документация CINT

Японские программисты давно доказали свое умение самостоятельно ставить и решать весьма нетривиальные задачи. С одним их детищем, к слову, тоже скриптовым языком, мы ранее знакомились (речь идет о Ruby). Но если Ruby (автор Йокихиро Мацумото) является классическим образчиком "еще одного языка" (отличающегося от своих предшественников как синтаксически, так и в части семантики привычных синтаксических конструкций), то сегодня мы будем говорить о куда более заурядной на первый взгляд программе с незамысловатым названием CINT (разработчик Масахару Гото -- Masaharu Goto). Аббревиатура CINT означает "C INTerpretator", т. е. "интерпретатор C". И, опять же, предваряя возможное недовольство читателя ("подумаешь, интерпретатор C..."), сразу стоит пояснить и смысл ранее использованного эпитета "уникальный", и оправданность его использования. Так что не спешите возмущаться...

Действительно, существует не одна реализация интерпретаторов языка C, и в этом смысле CINT не единственный и не уникальный (хотя это на самом деле не совсем так). Но... в оценке любой реализации, в отличие от оценки "чистой идеи", есть один существенный нюанс -- Как Это Реализовано. И здесь для начала стоит привести только один неоднократно проверенный факт -- CINT, написанный на языках программирования С (и частично С++), способен интерпретировать... самого себя. То есть, имея исполняемую программу CINT для выбранной вами платформы, вы можете интерпретировать ею исходные тексты CINT, из которых с помощью того или иного компилятора была получена сама программа CINT. И созданный таким образом "CINT в CINT" будет совершенно работоспособным. Это уникальное свойство открывает два главных качества CINT -- очень высокие степень близости к двум основным языкам программирования (С и С++) и качество разработки. Кроме того, CINT легально бесплатно доступен с открытыми исходными текстами, работает "на всем, что движется" (в перечне поддерживаемых платформ присутствуют фактически все актуальные Unix-совместимые ОС, Windows, DOS, BeOS и Mac OS/Mac OS X и даже такая по нынешним временам экзотика, как VAX VMS), позволяет прототипировать С/C++ программы "mainstream-платформ" -- с Win32 API и Posix API -- и, наконец, развивается более 12 лет...

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

Естественно, знакомство с CINT надо начинать с установки интерпретатора на свой компьютер. Получить исходные тексты программы и ее бинарные версии для некоторых платформ можно по адресу root.cern.ch/root/Cint.html или с одного из многочисленных "зеркал" проекта ROOT (некогда разработанный для компании Agilent, CINT "перекочевал" в состав масштабной объектно-ориентированной системы поддержки научного анализа данных ROOT, при этом не утратив значимости как самостоятельная разработка). Процедура установки системы для пользователей ОС Unix совершенно традиционна и потому не заслуживает внимания, пользователи ОС Windows могут собрать CINT с помощью имеющихся в поставке bat-файлов и компилятора С/С++ (примитивная процедура описана в файле README.txt) или поступить проще -- установить распространяемую бинарную версию, с одним маленьким "но": исполняемая версия CINT для ОС Windows помещается "поверх" исходных текстов, т. е. загружать надо оба дистрибутива! Последний этап процедуры инсталляции -- установка переменной окружения CINTSYSDIR, содержащей путь каталога, в котором развернута CINT (опять же маленькое замечание для ОС Windows -- учитывая кросс-платформенный характер CINT, целесообразно избегать в пути к инсталляции имен каталогов, содержащих пробелы или не-ANSI символы; оптимально разместить CINT просто в каталоге с:\cint). В частности, автором статьи CINT была неоднократно успешно и без проблем инсталлирована на платформы FreeBSD 4.4--4.7 и Windows 2000 с помощью компиляторов gcc 2.95--2.95.4 и Borland C++ 5.5.

Для удобства использования CINT в не Unix-совместимых системах понадобится хорошая реализация текстовой консоли. В частности, в ОС Windows можно для этих целей применить удачную легально бесплатную разработку Econsole. Собственно, на этом перечень основных технологических нюансов считается исчерпанным (а с "неосновными" всегда можно ознакомиться в документации и архивах переписки пользователей CINT по адресу root.cern. ch/root/cinttalk/AboutCint.html).

Теперь поговорим о "языковых нюансах". Несмотря на то что сам автор CINT в документации неоднократно отмечает, что цель достижения 100%-ной совместимости со стандартами ANSI/ISO C/C++ им не ставилась, все же интерпретатор "понимает" "most of K&R and ANSI C/C++ language constructs" (большинство конструкций языков С/С++ в версиях Кернигана--Ричи, а также соответствующих стандартам ANSI/ISO). Не стоит смущаться неопределенностью термина "большинство конструкций" -- в части интерпретации ANSI/ISO С CINT "понимает" почти все, по оценкам авторов документации и многочисленных пользователей системы ROOT -- примерно 90--95% от языка С, предписанного стандартами. Оставшиеся 5--10% несовместимости на самом деле являются не столько недостатком, сколько исключительно интересным фактором при учете нашего "нестандартного" подхода к рассмотрению CINT как самостоятельной конструкции. Использование CINT для прототипирования именно с учетом этих несовместимостей на деле оказывается даже... полезным для будущего компилируемого кода. Хотя бы потому, что несовместимости CINT лучше всего охарактеризовать простым правилом их избежания: "не пользуйтесь в CINT-прототипах слишком заумными конструкциями С, избегайте применения в качестве ключевых элементов вашей программы тех языковых конструкций, которые стандартом или Керниганом--Ричи отмечены как угрожающие мобильности, придерживайтесь хорошего стиля программирования". Уточняя это правило, стоит выделить самые важные девиации CINT от ANSI/ISO C. Все они, можно сказать, малосущественны -- от синтаксических до семантических. Синтаксические, например, требуют обязательного разделения хотя бы одним пробелом символов "*" и имен типа и переменной при объявлении указателей, т. е. объявления вида "int****iPtr;" считаются недопустимыми. Это требование CINT, по идее, нарушается в реальных программах С/C++ разве что участниками "С obfuscated code contest" -- создателями умышленно нечитаемого кода. Также к синтаксическим относятся требования обязательно использовать метку "default:" в case-конструкциях только последней в перечне возможных ветвлений.

К счастью, хороший стиль программирования на С/С++ требует именно такой формы записи оператора ветвления, и "упрятывание" метки default: в глубинах конструкции switch никогда никем не приветствовалось.

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

Семантические "отклонения от нормы" CINT тоже не сильно шокируют. Одна из главных девиаций связана с областями видимости локальных переменных. В ANSI/ISO С/С++ последние имеют область видимости "ограничивающий блок", что позволяет объявлять локальные переменные в началах блоков, например, операторов, изменяющих поток выполнения программы (if, for, while, do while), а после завершения блока повторно использовать эти же имена. В CINT область видимости локальной переменной -- "ограничивающая функция", что означает фактически следующее: вы можете объявлять переменные в соответствии с правилами ANSI/ISO C/C++, но их повторное объявление в пределах одной функции невозможно. Считать такое ограничение слишком суровым трудно, и при прототипировании программ лучше всего придерживаться традиционного правила структурного программирования: "Все переменные должны быть объявлены в начале функции". По крайней мере, следование ему вносит дополнительный порядок в исходные тексты, дисциплинирует и прекрасно "уживается" с требованиями последних версий стандартов. То же самое можно сказать и об ограничении на использование оператора переименования типа typedef -- его в CINT можно применять только на уровне модуля, т. е. вне тела любой функции. Впрочем, если "перекопать" реальные тексты C/C++ программ, переименования типов, локализованные в функциях, окажутся редкостью. Здравый смысл подсказывает, что в C подобный "фокус" не нужен просто потому, что именно модули, а не функции этого языка, используются для локализации операций над одним типом данных, а уж в С++ typedef вообще является рудиментарным наследием C. Более тонкие нюансы несовместимости CINT с ANSI/ISO С в большинстве случаев на нашем ознакомительном уровне можно вообще не принимать во внимание (если придерживаться вышеуказанного правила, эти нюансы никак не скажутся даже в самом активном процессе использования CINT). Что же касается вопросов совместимости CINT-кода с C++, следует отметить очень хорошую поддержку интерпретатором механизма шаблонов (естественно, здесь есть свои ограничения, но обеспечиваемый уровень совместимости можно считать более чем достаточным для решения задач прототипирования даже весьма сложных и нетривиальных программ), почти полное соответствие правил перегрузки функций требованиям стандарта, реализацию механизмов обработки исключительных ситуаций (exception handling) и пространств имен (namespace). Девиации CINT-подмножества стандартного языка С++ также обладают двойственным характером: с одной стороны, они ограничивают некоторые возможности программиста, с другой -- вынуждают отказываться от ряда "грязных трюков C++". Это утверждение касается, например, "нелюбви" CINT к указателям на функции, которые применяются в качестве аргументов для вызовов других функций, или к отсутствию реализации указателей на данные -- члены класса.

"Технологическая оснастка" интерпретатора CINT вполне самодостаточна -- он располагает встроенными командным отладчиком (напоминающим упрощенный до разумного уровня сложности gdb для Unix-совместимых систем) и краткой подсистемой помощи. Главные же особенности "оснастки" заключаются в пригодности CINT к встраиванию в приложения (например, в виде динамически разделяемой библиотеки) и к расширяемости самого интерпретатора сторонним исполняемым кодом, разработанным в соответствии с требованиями API CINT. Именно благодаря последнему свойству, CINT, создатель которого умышленно ограничил базовый набор встроенных библиотечных функций С соответствующим стандартам ANSI перечнем, поддерживает вызовы POSIX и Win32 API. К слову, "подстегивание" дополнительных интерфейсных библиотек, обеспечивающих расширенное использование возможностей целевой платформы, где работает CINT, -- задача относительно несложная для подготовленного программиста C/C++. Последнее, о чем хотелось бы упомянуть, -- пригодность CINT к интерпретации далеко немаленьких программ. Это, как и большинство свойств CINT, подтверждено многолетней эксплуатацией в составе пакета ROOT -- программа справляется со "скриптами" из десятков и даже сотен тысяч строк кода.

Итак, настало время подводить итоги. Но это уже задача, требующая от читателя самостоятельного решения. CINT -- действительно очень красивая и полезная разработка, для которой легко найти много применений -- от образовательной сферы до узкопрофессиональных приложений. Начинающим изучать языки C и C++ CINT поможет сравнительно быстро почувствовать их "на вкус", не загромождая бесспорно присущее этим инструментам изящество массой громоздких деталей, свойственных мощным системам программирования. Профессиональные программисты найдут применение CINT в качестве инструмента для разработки в стиле "что будет, если?" -- ведь до сих пор никто не отменил потребности в сложной нетривиальной алгоритмике, для которой полный технологический цикл "программирование -- трансляция -- отладка -- изменение программы -- ..." оказывается не то чтобы неподходящим, но утомительным. И, наконец, CINT -- хороший претендент на роль встраиваемой компоненты, но разработчикам надо обязательно учитывать специфику ее лицензии.