Чтобы начать делать что-то полезное с Roslyn , мы собираемся осмотреть синтаксическое дерево, найти что-то интересное — и затем изменить его! Сложная структура синтаксического дерева программы на C # ( класс SyntaxTree ) раскрывается через довольно интуитивную объектную модель, включающую три типа сущностей:
Узлы являются основными элементами языка; например, IfStatementSyntax — это узел, представляющий оператор «if», а LiteralExpressionSyntax — это узел, представляющий буквальное выражение.
Токены являются вторичными элементами, которые, тем не менее, очень важны, такими как идентификаторы, строковые литералы и числовые литералы. Токены всегда привязаны к узлу. Например, узел IfStatementSyntax будет иметь узел ExpressionSyntax в качестве своего свойства Condition , и это может оказаться узлом BinaryExpressionSyntax со свойствами Left и Right, описывающими больше узлов ExpressionSyntax, и токен OperationToken , описывающий операцию.
Пустяки — это все остальное — директивы препроцессора, пробелы, комментарии — на вершине токенов.
Синтаксическое дерево, конечно же, имеет родительско-дочерние отношения между узлами, и существует набор API для обхода этих отношений, например, DescendantNodes и FirstAncestor .
Синтаксические деревья могут быть легко созданы из исходного кода. Это также очень быстрый процесс, потому что не происходит привязка или передача кода — только лексер и анализатор участвуют в построении дерева. (В следующем посте мы также рассмотрим семантическую модель, которая требует построения и связывания символов.)
SyntaxTree tree = SyntaxTree.ParseCompilationUnit(@" using System; public class MyClass { public static void MyMethod() { Console.Write(""Hello There {0}"", 42); Console.Write(42); } } "); Console.WriteLine(tree.Root.GetFullText());
Проверка дерева в визуализаторе отладчика ( поставляется с Roslyn CTP в качестве примера ) показывает следующую структуру:
Вы можете проверять и изменять синтаксические деревья напрямую, но более простым способом было бы использовать класс посетителей, производный от SyntaxWalker или SyntaxRewriter . Демонстрация быстрого кода лучше, чем тысяча слов, описывающих его, поэтому вот переписчик, который изменит числовые литералы со значения 42 на значение 43:
/// <summary> /// Replaces the numeric literal 42 with the /// numeric literal 43. /// </summary> class MyLiteralRewriter : SyntaxRewriter { protected override SyntaxNode VisitLiteralExpression( LiteralExpressionSyntax node) { if (node.Kind == SyntaxKind.NumericLiteralExpression) { SyntaxToken token = node.Token; if (token.Value is int && (int)token.Value == 42) { return node.ReplaceToken( token, Syntax.Literal( token.LeadingTrivia, "43", 43, token.TrailingTrivia)); } } return node; } }
Обратите внимание, что метод VisitLiteralExpression не изменяет узел — он либо возвращает существующий узел, либо возвращает новый узел с новым литеральным токеном. Вся поверхность API Roslyn такая: все объекты неизменны, и вы создаете новые объекты на основе существующих.
Как этот посетитель применяется к синтаксическому дереву? Чтобы применить его, нам нужно дать ему корень дерева, и он вернет новый корень дерева. Этот новый корень дерева можно скомпилировать, проанализировать или просто … сериализовать в текст:
SyntaxNode newRoot = new MyLiteralRewriter().Visit(newRoot); tree = SyntaxTree.Create( tree.FileName, (CompilationUnitSyntax)newRoot); Console.WriteLine(tree.Root.GetFullText());
Само собой разумеется, что этот переписчик будет обнаруживать только числовые литералы — он не будет соответствовать числу 42, когда он появляется в комментариях или в строках, как может сделать более ошибочный подход на основе регулярных выражений.
В следующем посте мы рассмотрим несколько более сложного посетителя, переписывающего синтаксис, который потребует семантической модели кода (т. Е. Символов и их значений), а не только синтаксической информации.