Автор: Sergey Teplyakov

Roslyn очень классная штука, с помощью которой можно не только анализаторы делать, но и много других интересных штук.

Вот, например, сколько потребуется усилий, чтобы сделать раскрашиватель синтаксиса, который будет печатать C#-файл в консоль и подсвечивать ключевые слова, строковые литералы и т.п вещи?

Вот как это можно сделать.

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

1. Получение документа и синтаксического дерева

Существует несколько способов получить синтаксическое дерево из строки. Самый простой способ, вызвать метод CSharpSyntaxTree.ParseText. В этот раз этот подход не сработает, поскольку помимо синтаксического дерева классификатору (классу Classifier) нужно получить еще и Workspace и Document. Поэтому используем другой способ:private static Document CreateDocumentFrom(string sourceCode){var workspace = new AdhocWorkspace(),Solution solution = workspace.CurrentSolution,Project project = solution.AddProject('SyntaxHighlighter', 'SyntaxHighlighter', LanguageNames.CSharp),Document document = project.AddDocument('source.cs', sourceCode),return document,}

2. Классифицировать дерево с помощью класса Classifier

Метод Classifier.GetClassifiedSpansAsync возвращает список ClassifiedSpan список простых объектов значений (value objects) с парой свойств: TextSpan и ClassificationType, которые определяют диапазон в исходном коде и тип символа, такие как StringLiteral, Identifier etc.public static async Task PrintSourceAsync(string sourceCode){// Получаем документ по исходному коду Document document = CreateDocumentFrom(sourceCode),var syntaxTreeRoot = await document.GetSyntaxRootAsync(),// Getting classification for the document IEnumerable<,ClassifiedSpan>, classification = await Classifier.GetClassifiedSpansAsync(document, syntaxTreeRoot.FullSpan),// Получаем мапу между началом нового символа и цветом IDictionary<,int, ConsoleColor>, positionColorMap = classification.ToDictionary( c =>, c.TextSpan.Start, c =>, GetColorFor(c.ClassificationType)),

3. Вывести на консоль содержимое файла

Вывод на консоль достаточно прост. Перебираем все элементы, и если текущий символ относится к новому символу, то изменяем цвет консоли: Console.BackgroundColor = ConsoleColor.Black,// Iterating over each character in source file and printing it for (int charPosition = 0, charPosition <, sourceCode.Length, charPosition++) {// Проверяем, нужно ли изменять цвет консоли ConsoleColor newColor,if (positionColorMap.TryGetValue(charPosition, out newColor)) {Console.ForegroundColor = newColor, }Console.Write(sourceCode[charPosition]), }Console.ResetColor(),} // Конец метода PrintSourceAsync

Нужно еще метод, который будет получать цвет в зависимости от классификации символа и вывести текст на консоль. Для получения цвета подойдет простой switch:private static ConsoleColor GetColorFor(string classificatioName){switch (classificatioName) {case ClassificationTypeNames.InterfaceName:case ClassificationTypeNames.EnumName:case ClassificationTypeNames.Keyword:return ConsoleColor.DarkCyan,case ClassificationTypeNames.ClassName:case ClassificationTypeNames.StructName:return ConsoleColor.DarkYellow,case ClassificationTypeNames.Identifier:return ConsoleColor.DarkGray,case ClassificationTypeNames.Comment:return ConsoleColor.DarkGreen,case ClassificationTypeNames.StringLiteral:case ClassificationTypeNames.VerbatimStringLiteral:return ConsoleColor.DarkRed,case ClassificationTypeNames.Punctuation:return ConsoleColor.Gray,case ClassificationTypeNames.WhiteSpace:return ConsoleColor.Black,case ClassificationTypeNames.NumericLiteral:return ConsoleColor.DarkYellow,case ClassificationTypeNames.PreprocessorKeyword:return ConsoleColor.DarkMagenta,case ClassificationTypeNames.PreprocessorText:return ConsoleColor.DarkGreen,default:return ConsoleColor.Gray, }}

Все, метод PrintSourceAsync, и теперь его можно использовать для вывода C# кода на консоль с расцветкой синтаксиса. Вот пример запуска:

clip_image002

Подсветка синтаксиса далека от идеала, да и класс Classifier дает далеко не все типы узлов. Но расширить решение не так и сложно. В любом случае, впечатляет, что в 100 строках кода умещается очень даже толковый syntax highlighter.

З.Ы. Все исходники на гитхабе в репозитории PlayingWithRoslyn.

Помогла статья? Оцените её!
0 из 5. Общее количество голосов - 0
 

You have no rights to post comments

Дмитрий Крикунов

Публикую статьи, обучающие курсы и новости по программированию: алгоритмам, языкам (С++, Java), параллельному программированию, паттернам и библиотекам (Qt, boost).