Как стать автором
Обновить
0

Для тех, кому в IT-стартапе требуется разбор Си++-кода

Время на прочтение 17 мин
Количество просмотров 16K
Статья описывает открытую и бесплатную библиотеку VivaCore, позволяющую разбирать и анализировать код на языке Си/Си++. Библиотека может быть полезна разработчикам, начинающим свои стартапы в области создания таких инструментов как построение документации по коду, специфические расширения языка, подсчет метрик и так далее.

Вместо вступления. Хватит обсуждать пилы, давайте лучше что-то создавать


В рамках блога компании Intel я хочу поддержать тематику создания «сложных» IT стартапов. Мне интересней слышать и обсуждать именно такие темы. Возможно, со временем площадка ISN станет именно таким местом, где начнется обсуждение и зарождение новых стартапов в области параллельного программирования, создания собственных библиотек и других технологий подобного рода. А быть может и на Хабрахабаре станет поменьше постов о достижениях иностранных компаний и о распилах в наших, и появится побольше интересных статей о своем собственном опыте, о том, кто и что делает, как у него это получается.

Место VivaCore во вселенной


Сразу четко определим место библиотеки VivaCore. Библиотека VivaCore создана на основе другой открытой библиотеки OpenC++, которая не развивается уже многие годы [1]. VivaCore представляет собой набор заплаток, фиксов, костылей, подпорок и подпорочек, которые позволили библиотеке OpenC++ разбирать исходный код, компилируемый Visual C++ 8.0, 9.0, 10.0. То есть поддержать специфические расширения, а также новые конструкции, появившиеся в C++0x и реализованные в Visual C++ 10.0 [2].

VivaCore не является полноценной библиотекой. VivaCore развивается нами как механизм, на котором мы создаем статические анализаторы кода, входящие в PVS-Studio. То есть VivaCore эта часть проекта PVS-Studio, которую мы решили сделать открытой с целью дать возможность другим использовать улучшенный и расширенный вариант OpenC++.

У VivaCore есть масса недостатков. Не полностью возможен анализ некоторых шаблонных конструкций, отсутствует документация, невозможна работа с кодом в формате Unicode и так далее. Если вам требуется полноценная поддержка разбора и анализа языка Си/Си++, или вы планируете создавать свой компилятор/среду разработки, то библиотека VivaCore вам не подойдет. В этом случае следует воспользоваться профессиональными библиотеками, например — EDG. Цена лицензии подобной библиотеки колеблется в приделах $40,000 — $250,000 в год. Эта цена является слишком высокой для многих стартапных проектов, которые не готовы к таким вложениям, без уверенности в успехе проекта. В этом случае библиотека VivaCore может быть хорошим компромиссом. Она бесплатна, и хоть и не идеально, но на достаточно хорошем уровне позволяет осуществлять работу с Си/Си++. Этот уровень будет достаточен для большинства инструментов работы с кодом.

Следует упомянуть, что есть возможность попробовать построить свой проект на базе исходных кодов GCC или других открытых системах. У этих вариантов также будут как сильные, так и слабые стороны. Что-то будет реализовать проще, что-то сложнее. Правовые аспекты тоже вносят свою корректировку. Я просто хочу рассказать о библиотеке VivaCore, а исследование, сравнение и выбор предоставляется читателю.

Если вас библиотека VivaCore все же заинтересовала, то данная статья позволит познакомиться с ней поближе и расскажет о возможных сферах ее применения. Архив с исходным кодом (проект для VS2010) доступен для скачивания здесь: http://www.viva64.com/ru/vivacore-library/. Время от времени мы его обновляем. Только хочу заранее попросить не заваливать меня различными вопросами по работе с VivaCore, если это связано с обучением. Если у вас полноценный проект, то конечно я постараюсь подсказать или мы можем заключить договор по реализации требуемой вам функциональности. Я хочу подстраховаться от наплыва вопросов студентов, который возникает 2 раза в год, когда им выдают задания на курсовые/дипломы связанными с созданием парсеров и тому подобного. :)

Основные термины


Прежде чем продолжить, кратко дам определение некоторым терминам.

Препроцессирование — механизм, просматривающий входной ".c/.cpp" файл, исполняющий в нём директивы препроцессора, включающий в него содержимое других файлов, указанных в директивах #include и прочее. В результате получается файл, который не содержит директив препроцессора, все используемые макросы раскрыты, вместо директив #include подставлено содержимое соответствующих файлов. Файл с результатом препроцессирования обычно имеет суффикс ".i". Результат препроцессирования называется единицей трансляции.

Синтаксический анализ (парсинг) — это процесс анализа входной последовательности символов, с целью разбора грамматической структуры. Обычно синтаксический анализ делится на два уровня: лексический анализ и грамматический анализ.

Лексический анализ — процесс обработки входной последовательности символов с целью получения на выходе последовательности символов, называемых лексемами (или «токенами»). Каждую лексему условно можно представить в виде структуры, содержащей тип лексемы и, если нужно, соответствующее значение. Для языка Си++ лексемами будут «class», «int», "-", "{" и так далее.

Грамматический анализ (грамматический разбор) — это процесс сопоставления линейной последовательности лексем (слов) языка с его формальной грамматикой. Результатом обычно является дерево разбора или абстрактное синтаксическое дерево.

Абстрактное синтаксическое дерево (Abstract Syntax Tree — AST) — конечное, помеченное, ориентированное дерево, в котором внутренние вершины сопоставлены с операторами языка программирования, а листья с соответствующими операндами. Таким образом, листья являются пустыми операторами и представляют только переменные и константы. Абстрактное синтаксическое дерево отличается от дерева разбора (derivation tree — DT или parse tree — PT) тем, что в нём отсутствуют узлы для тех синтаксических правил, которые не влияют на семантику программы. Классическим примером такого отсутствия являются группирующие скобки, так как в AST группировка операндов явно задаётся структурой дерева.

Метапрограммирование — создание программ, которые создают другие программы как результат своей работы, либо изменяющие или дополняющие себя во время выполнения [3]. В метапрограммировании можно выделить два основных направления: генерация кода и самомодифицирующийся код. Далее мы будем рассматривать метапрограммирование как генерацию исходного кода на языке Си/Си++.

Обход синтаксического дерева — обход по всем вершинам и листьям синтаксического дерева с целью сбора информации различного рода, анализа или модификации.

Что такое VivaCore


Библиотека VivaCore представляет собой проект с открытым программным кодом, построенный на базе более старой библиотеки — OpenC++ (OpenCxx). Библиотека VivaCore реализована на языке Си++ и представляет собой проект, предназначенный для компиляции в Visual Studio 2010. Однако никакие специфические расширение компилятора Visual C++ не используется и проект после небольшой адаптации можно будет собрать другим современным компилятором.

Библиотека VivaCore создана и развивается сотрудниками ООО «Системы программной верификации». На библиотеку анализа кода VivaCore имеется свидетельство о государственной регистрации программ для ЭВМ N 2008610480.

Вы можете свободно и бесплатно использовать библиотеку VivaCore. Единственным лицензионным ограничением является необходимость указать, что ваш проект разработан на основе библиотек OpenC++ и ее расширения — VivaCore.

В первую очередь библиотека VivaCore может быть интересна небольшим компаниям (стартапам), которые создают или планируют создавать инструменты для работы с кодом. Перечислить все допустимые области и методы применения, естественно, не возможно, но я все-таки назову ряд направлений, чтобы показать VivaCore под разными углами зрения. В скобках в качестве пояснения указаны примеры продуктов, относящиеся к данному классу решений. Итак, с помощью VivaCore возможно разработать:
  1. инструменты рефакторинга кода (VisualAssist, DevExpress Refactoring, JetBrains Resharper);
  2. статические анализаторы общего и специализированного назначения (Viva64, lint, Gimpel Software PC-Lint, Parasoft C++test);
  3. динамические анализаторы кода (Compuware BoundsChecker, AutomatedQA AQTime);
  4. расширения языков Си/Си++, в том числе для поддержки метапрограммирования (OpenTS);
  5. автоматизированное тестирование кода (Parasoft C++test)
  6. трансформации кода, например, для оптимизации;
  7. подсветку синтаксиса (Whole Tomato Software VisualAssist, любая современная среда разработки);
  8. системы построения документации по коду (Synopsis, Doxygen);
  9. инструменты контроля изменений в исходном коде или анализа эволюции изменений;
  10. поиск дублирующегося кода на уровне грамматических конструкций языка;
  11. подсчет метрик (C and C++ Code Counter — CCCC);
  12. поддержку стандартов кодирования (Gimpel Software PC-Lint);
  13. инструменты, облегчающие миграцию кода на другие программные и аппаратные платформы (Viva64);
  14. автоматическую генерацию кода;
  15. визуализаторы кода, системы построения диаграмм зависимостей (Source-Navigator, CppDepend);
  16. форматирование кода (Ochre SourceStyler).

Отличие библиотеки VivaCore от библиотеки OpenC++


Основное отличие библиотеки VivaCore от OpenC++ заключается в том, что она является живым проектом и продолжает активно наращивать функциональность. Библиотека OpenC++, к сожалению, давно не развивается. Самое последнее изменение библиотеки датируется 2004 годом. А последнее изменение, связанное с поддержкой новых ключевых слов, датируется 2003 годом. Этим исправлением является неудачная попытка добавить тип данных wchar_t, что внесло пять ошибок различного типа.

Перечислим новые ключевые функциональные возможности, реализованные в библиотеке VivaCore, по сравнению с OpenC++:
  1. Поддержан классический язык Си. Используется другой набор лексем, что дает возможность именовать переменные именем «class» или объявить функцию в классическом Си стиле: PureC_Foo(ptr) char *ptr; {… }.
  2. Проделана большая работа по поддержке специфики синтаксиса языка Си++, используемого при разработке в среде VisualStudio 2005/2008/2010. Например, библиотека обрабатывает ключевые слова __noop, __if_exists, __ptr32, __pragma, __interface и так далее.
  3. Поддержаны некоторые новые конструкции, имеющиеся в стандарте языка Си++ от 1998 года, но не успевшие попасть в OpenC++. В частности поддержан вызов шаблонных функций с использованием слова template: object.template foo<int>();.
  4. Поддержан стандарт языка C++0x на том уровне, на котором его поддерживают компиляторы Visual C++ и Intel C++.
  5. Реализовано вычисление значений литеральных констант.
  6. Библиотека адаптирована и оптимизирована для работы на 64-битных системах.
  7. Исправлено большое количество ошибок и недочетов. Их очень много и перечислять их здесь, нет никакой невозможности.
  8. Поддержан разбор директив OpenMP. Правда большую часть работы с ними выполняет код VivaMP, который отсутствует в VivaCore. Но если что, пишите — подскажем, поможем.
  9. Реализовано кодирование длинных типов. Ранее любой тип кодировался специальной строкой не длиннее 127 символов, что временами было недостаточно. Как результат, на таких библиотеках как boost или loki, библиотека OpenC++ «сходила с ума» и работала некорректно.

Общая структура библиотеки VivaCore


Общая функциональная структура библиотеки VivaCore показана на рисунке 1.

Рисунок 1. Общая структура библиотеки VivaCore.

Рисунок 1. Общая структура библиотеки VivaCore.

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

Рисунок 2 - Последовательность обработки кода.

Рисунок 2 — Последовательность обработки кода.

1) Подсистема ввода данных (Input subsystem)


Библиотека VivaCore может корректно использовать только исходный Си/Си++ код, предварительно обработанный препроцессором. Сейчас для генерации препроцессированных файлов в PVS-Studio исползуется компилятор Visual C++. После его работы получается обработанный файл с расширением «i», с которым и работает VivaCore.

В определенных случаях можно подать на вход необработанные Си/Си++ файлы, но в этом случае работать с VivaCore следует не дальше уровня разбиения файла на лексемы. Этого вполне может хватить для подсчета метрик или иных целей. Но пытаться строить и анализировать дерево разбора (PT) не стоит, поскольку результат, скорее всего, будет малопригоден для обработки.

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

2) Подсистема предварительной обработки кода (Preprocessor subsystem)


Хочется подчеркнуть, что данная подсистема не выполняет препроцессирование кода в его классическом понимании. Как было сказано ранее, препроцессированный код уже должен быть подан на вход библиотеки VivaCore. Рассматриваемая подсистема служит для следующих задач:
  • Разбиение текста программы на строки и разбиение их на две логических группы. К первой группе относится системный код (код библиотек компилятора и так далее). Ко второй пользовательский код, который представляет интерес для анализа. В результате, разрабатывая статический анализатор, пользователь получает возможность решать, будет ли он анализировать код системных библиотек или нет.
  • Специализированная модификация текста программы в памяти. Примером может служить удаление из кода конструкций конкретной среды разработки, не имеющих отношения к языкам Си или Си++. Например, анализатор Viva64 при своей работе убирает такие ключевые конструкции, как SA_Success или SA_FormatString, имеющиеся в заголовочных файлах Visual Studio и предназначенные для статического анализатора, встроенного в Visual Studio (речь идет об Code Analysis for C/C++).

3) Лексический анализатор (Lexer)


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

Лексический анализатор VivaCore разбирает текст программы на набор объектов типа Token (см. файл Token.h), которые содержат информацию о типе лексемы, ее местонахождении в тексте программы и длину. Типы лексем перечислены в файле tokennames.h. Примеры типов лексем:

CLASS — ключевое слово языка «class»

WCHAR — ключевое слово языка «wchar_t»

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

При добавлении лексем необходимо объявить их в файле tokennames.h и добавить в таблицы «table» / «tableC» / tableC0xx в файле Lex.cc. Первая таблица предназначена для обработки Си++ файлов, а вторая — для Си файлов, третья для С++0x. Причина наличия нескольких таблиц связана с тем, что набор лексем в языке Си, Си++, С++0x различен. Например, в языке Си отсутствует лексема CLASS, так как в Си слово «class» не является ключевым и может обозначать имя переменной.

В качестве эксперимента, изучения VivaCore, или практических целей, вы можете может получить список лексемы в виде неструктурированного текста или используя функцию DumpEx в следующем отформатированном виде:

258  LC_ID      5
258  lc_id      5
91   [          1
262  6          1
93   ]          1
59   ;          1
303  struct     6
123  {          1
282  char       4
42   *          1
258  locale     6

4) Грамматический анализатор (Parser)


Грамматический анализатор предназначен для построения дерева разбора (derivation tree — DT), которое в дальнейшем может быть подвергнуто анализу и трансформации. Обратите внимание, что грамматический анализатор библиотеки VivaCore строит не абстрактное синтаксическое дерево (АST), а именно дерево разбора. Это позволяет более просто осуществить поддержку метапрограммных конструкций, которые могут быть добавлены пользователем в язык Си или Си++.

Построение дерева в библиотеке VivaCore происходит в функциях класса Parser. Узлами и листьями дерева являются объекты, классы которых наследуются от базовых классов NonLeaf и Leaf. На рисунке 3 показана часть иерархии классов, используемых для представления дерева.

Рисунок 3. Часть иерархии классов, используемых для построения дерева разбора.

Рисунок 3. Часть иерархии классов, используемых для построения дерева разбора.

Как видно из рисунка, класс Ptree является базовым классом для всех остальных и служит для организации единого интерфейса для работы с другими классами. В классе Ptree имеется набор чистых виртуальных функций, реализуемых в потомках. Например, функция «virtual bool IsLeaf() const = 0;» реализуется в классах NonLeaf и Leaf. Практически классы реализуют только эту функцию и нужны для того, чтобы сделать иерархию классов более логичной и красивой.

Поскольку работа с деревом занимает существенный объем библиотеки, то в Ptree имеется большой набор функций для работы с узлами дерева. Для удобства эти функции являются аналогами функций работы со списками в языке Lisp. Вот некоторые из них: Car, Cdr, Cadr, Cddr, LastNth, Length, Eq.

Чтобы получить общее представление о работе грамматического анализатора, в качестве примера приведем дерево разбора, которое будет построено из следующего кода:

int MyFoo(const float value)
{
  if (value < 1.0)
    return sizeof(unsigned long *);
  return value * 4.0f < 10.0f ? 0 : 1;
}


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

Рисунок 4.1. Цветовые обозначения узлов семантического дерева.

Рисунок 4.1. Цветовые обозначения узлов семантического дерева.

Рисунок 4.2. Представление заголовка функции.

Рисунок 4.2. Представление заголовка функции.

Рисунок 4.3. Представление тела функции.

Рисунок 4.3. Представление тела функции.

Рисунок 4.4. Представление тела функции.

Рисунок 4.4. Представление тела функции.

Следует упомянуть еще один важный компонент работы анализатора. Это получение информации о типах различных объектов (функциях, переменных и так далее), что осуществляется в классе Encoding. Информация о типе представляется в виде специально закодированной строки, с форматом которой можно познакомиться в файле Encoding.cc. В библиотеке существует также специальный класс TypeInfo, позволяющий извлекать информацию о типах. Например, используя такие функции как IsFunction, IsPointerType, IsBuiltInType можно легко идентифицировать тип обрабатываемого элемента.

Описание подходов к добавлению новых типов узлов или листьев является нетривиальной задачей и не может быть изложено в этой обзорной статье. Рациональным решением будет выбор одного из классов, например PtreeExprStatement и просмотр всех мест в коде, где происходит создание объектов данного класса, работа с ними и так далее.

Полученное по завершению дерево разбора может быть сохранено в формате ".с/.cpp" файла, что, впрочем, имеет мало смысла. Эта возможность обретет смысл после изменения дерева разбора, которое может произойти на следующих этапах. Сохранив дерево сейчас в виде кода программы, мы получим ровно то, что получили на входе. Впрочем, это может быть вполне полезно для тестирования изменений, внесенных в лексер и парсер.

Больший интерес представляет возможность сохранить дерево для дальнейшей обработки в произвольном формате, реализованном пользователем. Примером может служить следующее текстовое представление кода, который был приведен ранее:

PtreeDeclaration:[
  0
  NonLeaf:[
    LeafINT:int
  ]
  PtreeDeclarator:[
    Leaf:MyFoo
    Leaf:(
    NonLeaf:[
      NonLeaf:[
        NonLeaf:[
          LeafCONST:const
          NonLeaf:[
            LeafFLOAT:float
          ]
        ]  
        PtreeDeclarator:[
          Leaf:value
        ]
      ]
    ]
    Leaf:)
  ]
  [{  
    NonLeaf:[
      PtreeIfStatement:[
        LeafReserved:if
        Leaf:(
        PtreeInfixExpr:[
          LeafName:value
          Leaf:<
          Leaf:1.0
        ]
        Leaf:)
        PtreeReturnStatement:[
          LeafReserved:return
          PtreeSizeofExpr:[
            Leaf:sizeof
            Leaf:(
            NonLeaf:[
              NonLeaf:[
                LeafUNSIGNED:unsigned
                LeafLONG:long
              ]
              PtreeDeclarator:[
                Leaf:*
              ]
            ]
            Leaf:)
          ]
          Leaf:;
        ]
      ]
      PtreeReturnStatement:[
        LeafReserved:return
        PtreeCondExpr:[
          PtreeInfixExpr:[
            PtreeInfixExpr:[
              LeafName:value
              Leaf:*
              Leaf:4.0f
            ]
            Leaf:<
            Leaf:10.0f
          ]
          Leaf:?
          Leaf:0
          Leaf::
          Leaf:1
        ]
        Leaf:;
      ]
    ]
    Leaf:}
  }]
]


Данный формат показан просто для примера.

5) Обход дерева разбора


Для разработчиков статических анализаторов кода или систем построения документации по коду наибольший интерес должен представлять этап обхода дерева разбора, осуществляемый с использованием классов Walker, ClassWalker, ClassBodyWalker. Обход дерева разбора можно осуществлять несколько раз, что позволяет создавать системы, модифицирующие код за несколько проходов, или проводить анализ, учитывающий уже накопленные знания при предыдущих обходах дерева.

Класс Walker служит для обхода базовых конструкций языка Си/Си++.

Класс ClassWalker наследуется от класса Walker и добавляет функциональность, связанную со спецификой классов, присутствующих в языке Си++.

Примечание. Если честно, еще в OpenC++ функциональность этих классов была смешана, а в VivaCore классы Walker и ClassWalker срослись еще больше. Их можно объединить в один, но смысла от такой работы нет.

Когда необходимо разобрать тело класса, то временно создаются и используются объекты класса ClassBodyWalker.

Если не вносить никаких изменений в библиотеку VivaCore, то будет происходить простой проход по всем элементам дерева. При этом само дерево не будет изменяться.

Если пользователь реализует функциональность, которая будет модифицировать вершины дерева, библиотека может перестроить дерево. Для примера рассмотрим код, транслирующий унарные операции:

Ptree* ClassWalker::TranslateUnary(Ptree* exp)
{
  using namespace PtreeUtil;
  Ptree* unaryop = exp->Car();
  Ptree* right = PtreeUtil::Second(exp);
  Ptree* right2 = Translate(right);
  if(right == right2)
    return exp;
  else
    return
      new (GC_QuickAlloc)
      PtreeUnaryExpr(unaryop, PtreeUtil::List(right2));
}


Обратите внимание, что если, транслируя выражение, стоящее справа от унарной операции, полученное дерево будет изменено, то будет изменен (пересоздан) и узел унарной операции. Что в свою очередь может повлечь перестройку и вышестоящих узлов.

Для наглядности рассмотрим этот пример более подробно.

Начинается обработка узла, представляющего собой унарную операцию над некоторым выражением и имеющего тип PtreeUnaryExpr. Первым элементом в списке, который извлекается с помощью операции exp->Car(), является непосредственно унарная операция. Вторым элементом, извлеченным с помощью PtreeUtil::Second(exp), является выражение, к которому применяется унарная операция.

Происходит трансляция выражения и результат помещается в переменную right2. Если этот адрес отличается от имеющегося, то это означает, что выражение было изменено. В этом случае происходит создание нового объекта типа PtreeUnaryExpr, которое и будет возвращено из функции TranslateUnary. В противном случае ничего не изменяется и возвращается тот же объект, что и поступил на вход.

Если пользователю потребуется производить сбор информации при обходе дерева, или производить его модификацию, то самым естественным образом будет наследоваться от классов ClassWalker и ClassBodyWalker.

Покажем самый просто пример, взятый из статического анализатора Viva64, в котором происходит специализированный анализ при проходе через оператор «throw»:

Ptree* VivaWalker::TranslateThrow(Ptree *p) {
  Ptree *result = ClassWalker::TranslateThrow(p);
  Ptree* oprnd = PtreeUtil::Second(result);
  // Если oprnd == nullptr, то это "throw;".
  if (oprnd != nullptr) { 
    if (!CreateWiseType(oprnd)) {
      return result;
    }
    if (IsErrorActive(115) &&
        !ApplyRuleN10(oprnd->m_wiseType.m_simpleType))
    {
      AddError(VivaErrors::V115(), p, 115);
    }
  }
  return result;
}


Вначале с помощью ClassWalker::TranslateThrow(p) выполняется стандартная трансляция узла. После чего выполняется необходимый анализ. Все просто и очень изящно.

Говоря об обходе дерева, следует также сказать об очень важном классе Environment, обеспечивающем получение информаций о типах различных объектов в различных областях видимости.

Пример использования класса Environment, представленного объектом env для получения типа объекта declTypeInfo:

TypeInfo declTypeInfo;
if (env->Lookup(decl, declTypeInfo)) {
  ...
}

6) Поддержка метапрограммирования


Существуют языки, для которых метапрограммирование является естественно составной частью. Примером является язык Nemerle, с которым можно познакомиться в статье «Метапрограммирование в Nemerle» [4]. Но в случае с Си/Си++ все сложнее, и в них метапрограммирование реализуется следующими двумя путями:
  1. Шаблоны в Си++ и препроцессор в Си. Данный путь имеет множество ограничений.
  2. Внешние языковые средства. Язык генератора составляется так, чтобы автоматически или с минимальными усилиями со стороны программиста реализовывать правила парадигмы или необходимые специальные функции. Фактически создается более высокоуровневый язык программирования. Для создания подобной системы как раз и может быть использована библиотека VivaCore.

Библиотека OpenC++, на которой построена VivaCore, изначально задумывалась именно для преобразования Си++ кода. Библиотека была частью некой системы, позволяющая использовать специфический вариант языка Си++.

Также на основе OpenC++ в Институте программных систем РАН была создана среда исполнения OpenTS для языка программирования T++. Это язык Си++, в который введены дополнительные конструкции для автоматического распараллеливания участков кода. Для простоты назову это неким аналогом технологии OpenMP. Данный пример демонстрирует возможность использования библиотеки OpenC++ для задач метапрограммирования. Библиотека VivaCore соответсвенно унаследовала эти возможности.

Под метапрограммированием в рамках библиотеки VivaCore следует понимать возможность расширения синтаксиса и функциональности языка Си/Си++ с целью создания собственного языка программирования. Новый метаязык можно реализовать в качестве промежуточного звена между препроцессором и компилятором. В общем случае схему функционирования можно представить, как показано на рисунке 5.

Рисунок 5. Участие транслятора метаязыка в процессе компиляции.

Рисунок 5. Участие транслятора метаязыка в процессе компиляции.

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

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

После обработки, новое дерево может сохранено в текст программы на языке Си/Си++, который затем будет собран с помощью компилятора.

Примечание. Чтобы не обманывать читателей, сразу скажу, что к сожалению это все теоретически. Мы не тестировали как множество наших правок и усовершенствований OpenC++ отразились на механизмах преобразованиях программ. Мы не используем этот механизм и как следствие не тестируем. К сожалению, я уверен, что в нем есть ошибки и недоделки, в результате которых текст программы на выходе будет не соответствовать тексту программы на входе. Я практик, не верю в удачу в таких делах и знаю что ошибки там должны быть. Поэтому, если Вы начнете создавать инструмент трансформации будьте к этому готовы и не ругайте нас. Лучше пишите, быть может вместе мы улучшим мир (в смысле библиотеку).

Подробнее по вопросам метапрограммирования можно обратиться к имеющейся документации к библиотеки OpenC++ [5].

7) Сохранение результатов


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

Демонстрационный проект VivaVisualCode


Чтобы нагляднее показать, как можно использовать библиотеку VivaCore, нами был создан демонстрационный проект VivaVisualCode, доступный для скачивания по адресу http://www.viva64.com/ru/vivacore-library/.

VivaVisualCode графически отображает построенное дерево разбора и позволяет посмотреть некоторую информацию по его узлам.

Рисунок 6. Пример построенного программой VivaVisualCode дерева разбора для кода &quot;float Value = 10.0 * 20.0;&quot;.

Рисунок 6. Пример построенного программой VivaVisualCode дерева разбора для кода «float Value = 10.0 * 20.0;».

Вместо заключения


Еще хочу предложить читателям статью Евгения Зуева "Редкая профессия" о его опыте разработки компилятора. Эта статья к данному посту никакого отношения не имеет, но весьма занятная, вот и рекомендую.

Библиографический список


  1. OpenC++ library. http://www.viva64.com/go.php?url=16
  2. Андрей Карпов. Статический анализ Си++ кода и новый стандарт языка C++0x. http://www.viva64.com/art-2-1-1708094805.html
  3. Джонатан Бартлет. Искусство метапрограммирования, Часть 1: Введение в метапрограммирование. http://www.viva64.com/go.php?url=39
  4. Kamil Skalski. Метапрограммирование в Nemerle. http://www.viva64.com/go.php?url=40
  5. Grzegorz Jakack. OpenC++ — A C++ Metacompiler and Introspection Library. http://www.viva64.com/go.php?url=41
Теги:
Хабы:
+35
Комментарии 17
Комментарии Комментарии 17

Публикации

Информация

Сайт
www.intel.ru
Дата регистрации
Дата основания
Численность
5 001–10 000 человек
Местоположение
США
Представитель
Анастасия Казантаева

Истории