Языки программирования и методы трансляции. С. Свердлов

Зачем вообще понадобилось читать книгу о языках и компиляторах?

Когда спустя много лет вновь разрабатываешь транслятор, то возникает желание свериться с рекомендациями из других источников кроме собственного вузовского багажа и опыта. Помочь в этом могут книги.

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

  • Д.Грис. Конструирование компиляторов для цифровых вычислительных машин
  • Н.Вирт. Построение компиляторов
  • Ахо, Сети, Ульман. Компиляторы. Принципы, технологии, инструменты
  • В.Касянов, И.Поттосин. Методы построения трансляторов
  • С.Свердлов. Языки программирования и методы трансляции

Монографии Д.Гриса и Н.Вирта - классика, которая может показаться излишне академичной и сверхлаконичной, особенно в исполнении маэстро Вирта. «Ну, это же тривиально!» (с). При этом Грис предлагает не полноценную книгу, а, скорее, хорошо скомпонованный сборник связанных статей.

Открыв последнюю, 2008 года, редакцию Ахо и компании я обнаружил, что ребята неплохо потрудились и наработали уже почти 1200 страниц! Однако, читать книгу «по диагонали» в поисках нужных мест оказалось труднее ожидаемого, выбор Явы в качестве языка примеров также нельзя признать удачным, поэтому отложил её до лучших времен. Книга В.Касьянова и И.Поттосина составляла основу нашего вузовского курса, я её пока не нашел в свободном виде, отложив в «шорт-лист». Посему выбор пал на книгу С.Свердлова.

Следуя собственным правилам чтения нехудожественной литературы, сформулированным в книге «Софтостроение изнутри» (прошу прощения у Сергея Свердлова за product placement), я вооружился листком бумаги формата А4, куда стал последовательно заносить все встретившиеся в тексте интересные моменты. Как видно на фотографии, этого листка, испещренного с двух сторон, оказалось недостаточно. Пришлось начинать новый!

Основное преимущество книги - краткость изложения при его полноте (около 250 страниц, собственно, последовательно изложенной сути вместо необходимости её вычленения из 1200 у Ахо), хорошие иллюстрации примерами структурного программирования. Хотя объектно-ориентированный подход и будет полезен для разработки транслятора, но для иллюстрации концептов, архитектуры, алгоритмов и приёмов обработки наличие в программе классов только усложняет изложение и структуру.

Я условно разбил книгу на 2 части: «лирику» (контекст и культура) и «физику» (теория и практика). В этой схеме и буду писать свои заметки.

Лирика...

Первая часть книги посвящена эволюции языков программирования, она составляет почти 200 страниц. В принципе, желающие сути могут сразу перейти к «физике», но я бы всячески не рекомендовал пропускать «лирический» текст. В нем изложен культурный код, который профессиональному программисту желательно иметь в прошитом виде в своём мозговом ЗУ. Тем же, кто перешел в категорию «ветеранов» будет приятно освежить в памяти страницы собственной биографии.

В послесловии к «Софтостроению изнутри» я попытался изложить эти же 200 страниц на пяти в виде притчи. Судя по отзывам, знающим историю понравилось, но некоторые просто не поняли зашитого в текст кода. Посему не премините и почитайте авторские мысли об эволюции программирования. Если Сергей отсчитывает поколения языков с Фортрана, то я - от мнемокода, назначая Фортран во «вторую систему». Вот, собственно, и все «противоречия». Не более, чем отсчет массива от нуля или единицы.

Поскольку свою первую программу автор написал еще в 1974 году, то несомненный интерес также представляют собой личные воспоминания об эволюции компьютерных технологий в СССР. Например, интересный факт, известный английский теоретик и практик системного программирования Ч. Хоар был в 1959 году аспирантом Московского государственного университета.

На стр.47 обнаружил ссылку на неизвестную мне доселе статью о языке PL/1 от моего хорошего давнего знакомого Сергея Бобровского. Вот так и открываются неизвестные страницы в биографии.

На стр.62 автор не удерживается от иронии («троллинга»), но опирается при этом на известное высказывание самого Дейкстры: «Практически невозможно научить хорошему программированию студентов, ориентированных первоначально на Бейсик: как потенциальные программисты они умственно оболванены без надежды на исцеление». Хотел бы добавить, что современные скриптовые языки типа Питона играют сейчас примерно ту же роль.

Однако, не будем забывать прекрасно сказанное: «матерью иронии является бессилие». Ирония Дейкстры по поводу Бейсика - не исключение.

На стр.112-113 автор также отмечает неоднократно озвученную в «Софтостроении» мысль, что наилучшая область использования ООП - имитационное моделирование (Симула-67) и графический интерфейс (Смолток). Само понятие «класс» появилось в языках программирования раньше понятия «запись». А из-за неструктурности ООП-программ их пользователям тогда пришлось вернуться к графическим блок-схемам!

На стр.142-143 в исчерпывающем описании свойств языка C# (Сишарп) есть отсылки к Алголу, Яве и даже Оберону. Однако, Delphi упомянут только в контексте свойств (критикуемых автором за излишестов на стр.152-153), хотя многие технологии Микрософт лицензировала именно у Борланд (точнее, уже у Inprise) за весьма круглую сумму в 125 млн.долларов (поиск по Microsoft Inprise deal 1999).

На стр.145 приводится удачный пример, как в Сишарпе при отсутствии констант и функций уровня пространств имен и необходимости использования using для сокращения длинных цепочек имен идентификаторов, можно нарваться на трудноотлавливаемую ошибку, не обнаруживаемую компилятором.

Стр. 157. Многословие Си# (как, впрочем, и Явы) выглядит непривлекательно и стилистически ущербно. Заимствованные из Си правила позволяют очень компактно записывать выражения и операторы, используя разнообразные специальные знаки. В то же время объектные нововведения оформлены громоздко и, наоборот, игнорируют возможности знаков препинания. В итоге получается, что и писать трудно, и читать нелегко.

Будучи согласным с автором в принципе, добавлю, что усложнение текста программ выгодно очень многим разработчикам, увеличивая их статус за счет так называемого «умения разбираться в коде». Неизбежный побочный эффект массовости прикладного программирования в ипостаси ремесла, а не инженерной дисциплины.

Критика автора не коснулась размещения функций непосредственно в теле класса, делающего текст и структуру программы трудночитаемыми без специальных инструментов. В Си++ и Объектном Паскале класс (и модуль) имеют интерфейсную часть, с которой удобно работать и которую удобно проектировать сверху-вниз.

К примерам на стр.163-178 у меня есть несколько замечаний инженерного толка, не снижающих их учебную ценность. Использование таких имен переменных, как int или real, ухудшает читаемость для программистов, использующих несколько языков в своей повседневной практике. Лучше будет брать в качестве имен нечто похожее на IntVal, FloatVal. На стр.164 код с выбором по цепочкам if-then-elseif будет содержать ошибки, если имеется наследование классов. Проблема решается в стиле Г.Буча, введением общего предка, что неизбежно приведет к необходимости множественного наследования при наращивании функциональности. Поэтому в пример просится и заключительное решение на основе интерфейсов, имеющее максимальную гибкость, хотя и проигрывающее обычному наследованию реализации по трудоемкости.

Отсутствие конструкторов в Обероне рассматривается как пример лаконичности спецификации языка. Однако, введение такого элемента непосредственно в язык, на мой взгляд, не только улучшило бы его читаемость. При наличии механизмов динамического создания объектов по ссылке на их тип, конструктор должен быть задан явно (может быть виртуальным) и иметь как минимум одну реализацию «по умолчанию». Иначе компилятору будет непонятно, что, собственно, нужно вызывать для создания объекта.

Вот иллюстрирующий необходимость конструкторов пример из Free Pascal (у всех наследников TShape конструкторы могут быть переопределены).

program Shapes;
 
type
  TShape = class
  public
    constructor Create; virtual;
  end;
 
  TShapeRef = class of TShape;
 
constructor TShape.Create;
begin
  writeln('TShape.Create');
end;
 
var
  ShapeRef: TShapeRef;
  Shape: TShape;
begin
  ShapeRef := TShape;
  Shape := ShapeRef.Create;
end.

На стр.196 приведена систематизирующая таблица классификации языков разработки для Веб в осях «внутри/вне HTML-документа» и «исполняется на сервере/на клиенте». Несмотря на краткость, такая классификация представляется исчерпывающей. Здесь же сделана отсылка к языкам, позволяющим, как Бейсик, писать небольшие программы непрофессиональным разработчикам.

На стр.197 в качестве провала технологии апплетов Ява приводится причина сложности самого языка. На мой взгляд, эта причина не столь важная, как проблема развертывания приложений. Именно поэтому судьбу Ява-апплетов повторил микрософтовский SilverLight, тогда как Flash-приложениям удалось достичь массового уровня на страницах сайтах.

На стр.199 приводится сводная таблица метрик языков программирования. Видно, что Сишарп догнал Аду, при том, что последняя предназначалась для профессионалов прежде всего оборонных отраслей, а Сишарп - универсальный язык в первую очередь для прикладного программирования, активно используемый в том числе и любителями.

Вот уж действительно, пути искусственной эволюции неисповедимы!

На стр.200 Сергей обращается к литературному творчеству, как к аналогу программирования. Эта же самая мысль изложена в главе «Литература и программное обеспечение» книги «Софтостроение изнутри». Приятно, когда разными путями приходят к одинаковым выводам.

...и физика

На стр.205 начинается вторая часть - изложение формальной теории, густо проиллюстрированное практическими примерами. Здесь интенсивность моих пометок немного снизилась, потому что теория - она и в Африке теория. И нет ничего практичней хорошей теории.

Способ «Домино де Ремера», о существовании которого до сего момента я и не подозревал, упомянутый на стр.215, представляется очень наглядным пособием по ручному разбору грамматик.

На стр.265 справедливо отмечается, что общего решения по обработке ошибок компиляции не существует. У меня возникали некоторые проблемы даже по обобщению лексического и синтаксических разборов (последний - с возвратами), тогда как для разных классов и типов грамматик такое общее решение вряд ли имеет практический смысл.

Несмотря на максимально простые примеры реализации составных частей транслятора, Сергею удаётся к месту вставлять в текст рекомендации «мойте руки перед едой», неочевидные и потому весьма полезные для начинающих. Так на стр.272 при сохранении интерфейса стек может быть реализован как списком, так и массивом.

На стр. 308 автор с сожалением отмечает, что в русском языке становятся привычными кальки с английских терминов, аналог которым уже, скорее всего и не сформируется. Скажу больше - наблюдаю вытеснение даже устоявшихся терминов их англоязычными кальками. Взять к примеру «юнит-тест» (модульный тест), «рефакторинг» (структуризация и факторизация) или «продакшн» (эксплуатация). Это естественный ход развития любого локального языка при внешней технологической зависимости. Известным своим квазирелигиозным отношением к языку французам этой тенденции тоже не удается избежать.

На стр.301 начинается описание проектирования и реализации компилятора учебного языка, являющегося сильно усеченным подмножеством Оберона. В структурно-модульном стиле, без явно ненужного здесь ООП, методом «сверху-вниз».

Модульный подход для такого простого однопроходного транслятора оптимален, так как число глобальных переменных минимально, фактически это только буфер потока лексем.

На стр.327 принято решение о пропуске комментариев. Здесь, видимо, имело бы смысл добавить, что комментарии могут быть нужны минимум в двух случаях:
- для программ автоматического переформатирования исходного текста
- при использовании структурированных комментариев для документирования кода

На той же странице приводится такая важная метрика кода, как число лексем. Действительно, привычная характеристика сложности программы, выраженная в строках кода, может быть обманчивой (стиль «больше одного оператора на строку» в ряде случаев оправдан), как и длина текста (из-за длин идентификаторов). Поэтому такая метрика представляется более объективной.

Стр.333, контекстный анализ. КС-грамматика хороша для построения автоматического анализатора, однако, многие контекстные зависимости в этом случае переходят на неявный уровень. Например, определить, является ли идентификатор в контексте выражения именем переменной или вызовом функции можно только по соответствующей таблице имен. Было бы заманчивым иметь возможность описания таких конструкций на уровне грамматики.

Стр.369, реализация виртуальной машины «ОВМ», здесь много отсылок к стековой архитектуре и Форту. Еще один повод начинающим хотя бы ознакомиться с языком Форт, чтобы понимать суть концепций. Лично мне изложение было приятно вдвойне, поскольку стековая архитектура использовалась у программируемых калькуляторов.

В ОВМ выбрана принстонская архитектура (фон Неймана), где инструкции и данные в памяти не разделены. Наверное, в рамках книги имело бы смысл добавить сведения и про гарвардскую архитектуру, где память инструкций и данных разделены. В частности, по ряду причин я выбрал для своего случая виртуальной машины именно гарвардскую.

Ближе к концу изложения автор обращается к системам автоматизированного построения трансляторов, таким как неразлучная парочка Lex/Yacc. Действительно, задав грамматику, можно мгновенно получить готовый код анализаторов, который необходимо нарастить семантическим анализом и генерацией кода для целевых платформ. Однако, Сергей справедливо отмечает, что ручное кодирование лексических и синтаксических анализаторов особенно для рекурсивного спуска, не представляет большой доли труда в общем зачете. При этом автоматически сгенерированный код практически нечитаем, а если вместить грамматику в LALR-разбор нельзя, применение инструментов также невозможно.

Транслятор учебного языка был реализован сразу на нескольких языках: Паскале, Си, Обероне, Яве и Сишарпе. Это не только иллюстрирует принципы кросс-трансляции (автор использует Т-диаграммы для иллюстрации раскрутки), но и позволяет получить множество сравнительных характеристик. На стр.400 эти характеристики сведены в таблицу.

Приложения содержат исходные тексты реализаций учебного транслятора на всех языках, думаю, никто не будет в обиде, несмотря на нескрываемое предпочтение автора к языкам Виртовской школы.

И еще немного лирики в конце. В эпиграфе Сергей посвящает книгу памяти своего отца. Именно это желание подвигло и меня на написание второй книжки про СУБД.

Нечасто, читая книги, тем более технические, встречаешь такое созвучие мыслей по многим вопросам. За что выражаю автору, С.Свердлову, особую признательность.