Сообщений 61    Оценка 646 [+1/-0]         Оценить  
Система Orphus

Проект R#

Автор: Чистяков Влад (VladD2)
The RSDN Group
Опубликовано: 28.01.2004
Исправлено: 10.12.2016
Версия текста: 1.0
Что?
Как?
Средства автоматизации
Основной workspace и проекты, входящие в него
CocoRssCLI
RSParser
Репозиторий CVS

Что?

Проект R# подразумевает создание следующих продуктов:

  1. Синтаксический и лексический парсер C#. Парсер принимает на вход текст синтаксически (но не обязательно семантически) совместимого с C# 2.0 (на первом этапе 1.2) модуля. Спецификацию C# 1.2 и 2.0 можно взять здесь (или на сайте Microsoft). Разбор должен производиться в синтаксическое дерево. Это дерево можно будет изменять путем замены, удаления или добавления узлов. Каждый узел дерева представляет некоторую синтаксическую конструкцию. Например, конструкция: «while (true)» разбирается в дерево, состоящее из оператора while и выражения true, вложенного в оператор while. Сам по себе оператор while может быть вложен в другой оператор или некоторый метод (свойство). Это позволит осуществить идеи, описанные в пункте 2, а также реализовать средства вроде броузеров кода (наподобие дерева классов из VS.NET) и интеллектуальных редакторов кода (вроде редактора той же VS.NET или #Builder-а). Лексер может быть использован для подкраски синтаксиса C# в различных приложениях.
  2. API, позволяющее модифицировать код, разобранный в синтаксическое дерево. Этот API должен позволять создавать от примитивных мета-приложений, самостоятельно находящих нужные куски абстрактного синтаксического дерева (Abstract Syntaxes Tree, AST) и «вручную» изменяющих его структуру, до высокоуровневых модулей, обеспечивающих преобразование кода по определенным правилам, задаваемым декларативно или в виде псевдоязыков (языков, имеющих синтаксис C#, но отличную от него семантику, например, логических языков или языков преобразований на подобии XSLT). С помощью таких высокоуровневых языков/препроцессоров можно будет создавать расширения языка вроде АОП-расширений, генераторов кода на базе шаблонов, функциональных языков, логических языков, синтаксических оптимизаторов, программ интеллектуального рефакторинга, профайлеров, преобразователей в другие языки (например, в MC++ или VB.NET), анализаторов кода и т.п.
  3. Компилятор языка R#, поддерживающий возможности, описанные в двух предыдущих пунктах. В придачу он должен обеспечивать генерацию MSIL, избавляя от необходимости пользоваться внешним компилятором C#. Также компилятор R# должен обеспечивать расширение синтаксиса по сравнению с C#. Пока что планируются следующие расширения: подключение реализации интерфейсов1 (замена множественного наследования, так называемые mixin-ы), всяческие преобразования1 (описанные в пп. 1 и 2), встроенные блоки ilasm (блоки низкоуровневого MSIL-кода, в которых будут доступны параметры и переменные, так, как это реализовано для ассемблерных вставок во многих компиляторах C++), модификатор readonly для локальных переменных и параметров1, параметры по умолчанию (аналогично тому, как это реализовано в VB.NET), мультиметоды (выбор метода в рантайме на базе информации о типах, переданных в метод), возможность обращаться к непрямым предкам (вызов в стиле base.base.Method()), inlining методов и свойств, АОП-функции... список открыт, предложения приветствуются :).
  4. Описанные вскользь в предыдущих пунктах высокоуровневые возможности вроде функциональных языков, генераторов кода, АОП-расширений и т.п.

1 – Будет доступно уже на стадии интеллектуального препроцессора (пункт 2).

Как?

Первая версия препроцессора R# (пункт 1) реализуется на обычном C# с помощью средств генерации парсеров.

Средства автоматизации

В качестве генератора выбран CocoR – генератор с открытым кодом, распространяемый без какой бы то ни было лицензии, и как мы предполагаем, совершенно фри :). Это так называемый LL(1)-генератор, но с расширениями, позволяющими превратить его в LL(K)-парсер за счет введения специального ключевого слова (IF), позволяющего разрешать LL(1)-конфликты в грамматике. CocoR выбран из-за хорошего качества реализации, простой отладки, и ввиду того, что он позволяет повысить качество выдаваемых сообщений об ошибках парсинга. К тому же это единственный парсер с открытым кодом, который мы нашли, не ограниченный варварскими лицензионными соглашениями наподобие GNU GPL. Качество реализации позволяет упростить модификацию как генератора парсеров, так и производимого им кода парсеров.

Грамматика в CocoR описывается в EBNF-нотации. С одной стороны, это позволяет упростить описание правил, но с другой, то, что спецификация C# содержит описание в форме обычной BNF, заставляет осуществлять дополнительные (непродуктивные) действия по преобразованию нотации BNF в EBNF. Решение проблемы облегчает то, что уже имеется довольно качественная реализация грамматики для C# в формате ATG (собственный формат описания лексики и грамматики CocoR, основанный на EBNF). В этой реализации уже решены большинство LL(1)- и других конфликтов. Реализация соответствует C# 1.1 (стандарту ECMA).

Ссылки по теме парсеров:

http://dotnet.jku.at/Projects/Rotor/ – эта страница содержит описание CocoR.

http://dotnet.jku.at/Projects/Rotor/2.0b/HowTo.html – и эта тоже. Собственно, ссылка на нее есть на предыдущей.

http://www.ssw.uni-linz.ac.at/Research/Projects/Coco/CSharp/ - это тоже описание. Оно более обширно, но не затрагивает некоторые нововведения.

http://se.math.spbu.ru/Courses/dotNETCompilerEngineering/DEFAULT.HTM – бесплатные статьи на русском языке. Рассказывается об общих принципах и т.п. Особенно советую прочитать про LL(K)-граматики/парсеры.

http://www.bolero.ru/product-2000843.html – хорошая книжка по теории построения компиляторов.

Также можно просто поискать по книжным сайтам по слову компиляторов.

Альтернативой LL(х)-парсерам являются парсеры, работающие по методу сдвиг/свертка, или LALR(1)-парсеры. Ярчайшим представителем этого класса является Yacc (yet another compiler of compiler). Они более быстры, и могут без вмешательства человека разбирать более сложные грамматики. Но у них есть и серьезные проблемы. Создание «разумных» сообщений об ошибках с их помощью затруднительно. Также в них очень тяжело производить отладку. Дело в том, что поиск в них ведется не методом рекурсивного спуска, а методом свертки. Управляет этим детерминированный конечный автомат (ДКА), создаваемый генератором парсера. Код ДКА практически нечитабелен (особенно в реализациях Yacc-ов). Входными данными к ДКА служат еще менее читабельные огромные массивы. Отладка проводится по довольно невнятному логу. В принципе, при наличии визуальной среды можно было бы радикально упростить отладку, но, к сожалению, таковой нет. Ну, а на обработку ошибок повлиять еще сложнее. Дело в том, что ДКА может только определить, нашлась ли та или иная конструкция в разбираемом файле. Это приводит к тому, что приходится создавать грамматику, угадывающую, где человек может ошибиться, а это не просто. С помощью Yacc-а или его последователей созданы компиляторы gcc (C/C++) и Моно C#. Оба они отличаются довольно невнятными сообщениями об ошибках, хотя тот же gcc существует очень давно.

В общем, для общего образования можно ознакомиться с этим методом и продуктами. Но мы выбрали в качестве парсера CocoR.

Основной workspace и проекты, входящие в него

CocoRssCLI

CocoR входит в состав решения (workspace) R# (носящего название CocoRssCLI-1.1.sln и CocoRssCLI-1.2.sln, для версий .NET 1.1 и 1.2 соответственно). Проект называется CocoRssCLI-1.1 (для .NET 1.1) или CocoRssCLI (для 1.2 – Whidbey). В него уже внесены некоторые изменения (и будут вноситься еще). Так, в CocoR заменен формат вывода сообщений об ошибках так, чтобы VS.NET позволяла переходить к соответствующий строке файла, содержащего ошибку.

RSParser

Решение содержит также проект RSParser (RSParser-1.1 для .NET 1.1) – это парсер, описанный в пункте 1 предыдущего раздела данной статьи.

Проект состоит из главного файла MainClass.cs (находящегося в корне проекта), содержащего тестовый код, инициализирующий сканер (Scanner, также называемый лексером или токенайзером), парсер (Parser), производящий поиск тестовых файлов (путь к которым задается в массиве paths) и последовательно скармливающий их парсеру.

Задача первой части данного проекта – привести парсер в рабочее состояние. В этом состоянии парсер должен без ошибок разбирать все тестовые примеры, находящиеся в папке Tests проекта, выдавать полноценные и верные диагностические сообщения на ошибки из тестовых примеров, находящихся в папке Errors проекта, а также без ошибок проходить все файлы проектов: Janus, SSCLI, Mono. В дальнейшем должен поддерживаться также парсинг проектов, содержащих код, удовлетворяющий спецификации C# 2.0.

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

Основные файлы проекта разбиты на две папки Engine и CodeDom.

Engine

Папка Engine содержит следующие файлы:

ПРЕДУПРЕЖДЕНИЕ

Перед компиляцией проекта убедитесь, что файлы Location.cs и Parser.cs. Вследствие непродуманности VS каким-то загадочным образом умудряется использовать при компиляции содержимое открытых (копий) файлов, а не их реальных образом хранящихся на диске. Таким образом, перегенерированные версии файлов не будут использоваться в процессе компиляции, и вы не будете понимать, почему изменения, только что внесенные вами, не срабатывают.

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

СОВЕТ

При изменении cs.ATG, Parser.frame, Scanner.frame, Makefile или Coco.exe (файла проекта CocoRssCLI) перед следующим запуском происходит автоматическая перегенерация файлов Parser.cs, Scanner.cs. Но иногда может понадобиться, чтобы произошла обязательная перегенерация этих файлов. Для этого можно перекомпилировать проект CocoRssCLI или изменить любой из перечисленных в начале этой подсказки файлов). Например, достаточно записаться в Makefile-файле. Однако это приведет к тому, что файлы нужно будет синхронизировать с репозиторием CVS.

ПРИМЕЧАНИЕ

Более подробные комментарии можно найти непосредственно в исходных файлах проектов. Обычно они даны в виде обычных комментариев или в виде автодокументирования. Создавая собственные методы или изменяя имеющиеся, не забывайте документировать процедуры, свойства, переменные и код. Комментарии должны носить логический характер. Не стоит писать, что в следующей строчке происходит вычитание одной переменной из другой (это все и так смогут понять). Следует описать, что делает следующий фрагмент кода логически. Так, для классов, методов и полей следует описывать их предназначение и список параметров. Для операторов if и switch писать в стиле «если найден такой-то элемент, то...», а что делается в случае успеха уже писать в строках внутри операторов. Если оператор if предусматривает конструкцию else, то старайтесь не делать отрицания внутри if (). Это плохо читается. Делайте проверку на верность, а в качестве комментария пишите «... то выполняем такую-то обработку». В else-части пишите «... иначе выполняем другую обработку». Еще раз повторюсь, описывайте суть процесса, а не действие.

Зачастую полезно в двух словах описать суть целого фрагмента кода, а не его отдельных частей. При этом сделайте отбивку в одну-две строки от предыдущего и следующего оператора.

Если комментарий не влезает в 78 символов, то перенесите не влезающую строку на следующую строку (причем с небольшим запасом, чтобы если нужно будет чуть-чуть подправить текст, не пришлось заниматься переформатированием строк).

Используйте комментарии /* */ только в cs.ATG. В нем они могут быть вложенными. В обычных .cs-файлах используйте строчные (//) комментарии. Это предотвратит проблемы со вложенностями и придаст коду единый стиль.

CodeDom

Эта папка содержит файлы, относящиеся к синтаксическому дереву – AST:

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

Репозиторий CVS

Исходные файлы проекта хранятся в отдельном репозитории CVS. Чтобы получить доступ к исходным файлам, нужно обратиться к Чистякову Владу (VladD2) по адресу vc(at)rsdn.ru или Корявченко Андрею (AndrewVK) avk(at)rsdn.ru. При этом нужно представиться (имя, фамилия в миру и логин на RSDN) и описать свое видение участия в проекте.

Репозиторий называется /rsharp, модуль для checkout-а – Parser. Адрес сервера – rsdn.ru (или IP 195.209.62.198). Для работы с репозиторием лучше использовать WinCvs. Более подробно см. статью Управление исходными текстами. Часть 1. Краткое руководство по CVS.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 61    Оценка 646 [+1/-0]         Оценить