Если прибегнуть к помощи какого-нибудь хорошего сетевого ресурса и попытаться
даже не проанализировать (анализ -- слишком серьезная задача), а просто оценить
все разнообразие интерпретируемых языков программирования, в той или иной степени
соответствующих неформальному сленговому определению "скриптовых",
можно сделать ряд очевидных "открытий". Например, взглянув на страницу
портала
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 -- хороший претендент на роль встраиваемой компоненты,
но разработчикам надо обязательно учитывать специфику ее лицензии.