Поделиться через


Работа с синтаксисом

Дерево синтаксиса — это фундаментальная неизменяемая структура данных, представленная API компилятора. Эти деревья представляют лексическую и синтаксическую структуру исходного кода. Они служат двумя важными целями:

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

Деревья синтаксиса

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

Замечание

RoslynQuoter — это средство с открытым исходным кодом, которое показывает вызовы API фабрики синтаксиса, используемые для создания дерева синтаксиса программы. Чтобы попробовать его в прямом эфире, см. http://roslynquoter.azurewebsites.net.

Деревья синтаксиса имеют три ключевых атрибута:

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

Дерево синтаксиса — это буквально структура данных дерева, в которой неконтерминальные структурные элементы являются родительскими другими элементами. Каждое дерево синтаксиса состоит из узлов, маркеров и тривии.

Узлы синтаксиса

Узлы синтаксиса являются одним из основных элементов деревьев синтаксиса. Эти узлы представляют собой синтаксические конструкции, такие как объявления, операторы, предложения и выражения. Каждая категория синтаксического узла представлена отдельным классом, производным от Microsoft.CodeAnalysis.SyntaxNode. Набор классов узлов не расширяем.

Все узлы синтаксиса — это нетерминальные узлы в дереве синтаксиса, что означает, что они всегда имеют другие узлы и токены в качестве дочерних. В качестве дочернего элемента другого узла каждый узел имеет родительский узел, к которому можно получить доступ через SyntaxNode.Parent свойство. Так как узлы и деревья неизменяемы, родительский элемент узла никогда не изменяется. Корень дерева имеет нулевого родителя.

Каждый узел имеет SyntaxNode.ChildNodes() метод, который возвращает список дочерних узлов в последовательном порядке на основе их положения в исходном тексте. Этот список не содержит токены. Каждый узел также имеет методы для проверки потомков, таких как DescendantNodes, DescendantTokens, или DescendantTrivia - которые представляют список всех узлов, токенов или служебной информации, которые существуют в поддереве, корнем которого является этот узел.

Кроме того, каждый подкласс узла синтаксиса открывает доступ ко всем тем же дочерним элементам через строго типизированные свойства. Например, BinaryExpressionSyntax класс узла имеет три дополнительных свойства, относящиеся к двоичным операторам: Left, OperatorTokenи Right. Тип Left и Right есть ExpressionSyntax, а тип OperatorTokenSyntaxToken.

Некоторые узлы синтаксиса имеют необязательные дочерние узлы. Например, у IfStatementSyntax есть необязательный ElseClauseSyntax. Если дочерний элемент отсутствует, свойство возвращает значение NULL.

Маркеры синтаксиса

Маркеры синтаксиса — это терминалы грамматики языка, представляющие наименьшие синтаксические фрагменты кода. Они никогда не являются родителями других узлов или маркеров. Маркеры синтаксиса состоят из ключевых слов, идентификаторов, литералы и препинания.

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

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

Свойство ValueText предоставляет ту же информацию, что и свойство Value, однако это свойство всегда имеет тип String. Идентификатор в исходном тексте C# может содержать символы экранирования Юникода, но синтаксис самой экранирующей последовательности не считается частью имени идентификатора. Поэтому, хотя необработанный текст, охватываемый токеном, включает управляющую последовательность, свойство ValueText этого не делает. Вместо этого он включает символы Юникода, определяемые escape-файлом. Например, если исходный текст содержит идентификатор, записанный как \u03C0, то это свойство маркера ValueText вернет π.

Тривиа синтаксиса

Тривиа синтаксиса представляют части исходного текста, которые в значительной степени незначительны для нормального понимания кода, таких как пробелы, комментарии и директивы препроцессора. Как и маркеры синтаксиса, тривия — это типы значений. Один Microsoft.CodeAnalysis.SyntaxTrivia тип используется для описания всех видов тривии.

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

Вы можете получить доступ к данным о структуре, исследуя коллекции SyntaxToken.LeadingTrivia или SyntaxToken.TrailingTrivia токена. При анализе исходного текста последовательности тривия связаны с маркерами. Как правило, маркер владеет любой тривией после него в той же строке до следующего маркера. Любая тривия после этой строки связана со следующим токеном. Первый токен в исходном файле включает все начальные служебные элементы, а последняя последовательность таких элементов в файле присоединяется к токену конца файла, который иначе не имеет ширины.

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

Диапазоны

Каждый узел, токен или тривиа знает свою позицию в исходном тексте и количество символов, из которых он состоит. Позиция текста представлена как 32-разрядное целое число, индекс которого начинается с нуля и обозначен как char. TextSpan Объект — это начальная позиция и количество символов, представленных как целые числа. Если TextSpan имеет нулевую длину, это обозначает расположение между двумя символами.

Каждый узел имеет два TextSpan свойства: Span и FullSpan.

Свойство Span — это диапазон текста от начала первого маркера в поддереве узла до конца последнего маркера. Этот диапазон не включает никаких начальных или конечных тривии.

Это FullSpan текстовый диапазон, включающий обычный диапазон узла, а также диапазон любой начальной или конечной тривии.

Рассмотрим пример.

      if (x > 3)
      {
||        // this is bad
          |throw new Exception("Not right.");|  // better exception?||
      }

Узел инструкции внутри блока имеет интервал, указанный одной вертикальной полосой (|). Он содержит символы throw new Exception("Not right.");. Полный диапазон обозначается двойными вертикальными полосами (||). Он включает те же символы, что и диапазон, и символы, связанные с ведущим и конечным тривией.

Виды

Каждый узел, токен или тривия имеет SyntaxNode.RawKind свойство типа System.Int32, определяющее точный элемент синтаксиса, представленный. Это значение можно преобразовать в языко-зависимое перечисление. Каждый язык, C# или Visual Basic, имеет одно SyntaxKind перечисление (Microsoft.CodeAnalysis.CSharp.SyntaxKind и Microsoft.CodeAnalysis.VisualBasic.SyntaxKind соответственно), которое перечисляет все возможные узлы, маркеры и лишние символы в грамматике. Это преобразование можно сделать автоматически, получив доступ к методам CSharpExtensions.Kind или методам расширения VisualBasicExtensions.Kind.

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

Например, у одного BinaryExpressionSyntax класса есть Leftи OperatorTokenRight дочерние элементы. Свойство Kind определяет, является ли это узлом синтаксиса типа AddExpression, SubtractExpression или MultiplyExpression.

Подсказка

Рекомендуется проверить типы с помощью IsKind методов расширения (для C#) или IsKind (для VB).

Ошибки

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

  • Если средство синтаксического анализа ожидает определённый тип токена, но не находит его, оно может вставить отсутствующий токен в дерево синтаксиса в том месте, где ожидался токен. Отсутствующий токен представляет собой ожидаемый токен, но он имеет пустой диапазон, и его свойство возвращает SyntaxNode.IsMissingtrue.

  • Средство синтаксического анализа может пропускать токены до тех пор, пока не найдет тот, на котором можно продолжить синтаксический анализ. В этом случае пропущенные маркеры присоединяются как узел trivia с типом SkippedTokensTrivia.