Создание закорючек с использованием вашего абстрактного синтаксического дерева

#c# #.net #parsing #abstract-syntax-tree

#c# #.net #синтаксический анализ #абстрактное синтаксическое дерево

Вопрос:

Ситуация:

Я создал сканер, синтаксический анализатор и различные классы AST для малоиспользуемого языка программирования, моего хобби-проекта. Анализатор с помощью сканера создает гетерогенный AST, с которым я выполняю некоторые манипуляции. В прошлом я создавал плагины / надстройки для некоторых IDE для подсветки синтаксиса и некоторых других элементов.

Проблема заключается в ошибках: анализатор генерирует некоторые и имеет доступ к токенам, которые составляют оператор. Однако некоторые ошибки возникают только позже, например, из-за невозможности разрешить идентификатор. Я хотел бы отображать закорючки под такими идентификаторами или другими ошибочными токенами. Не только это, мне нравится возможность манипулировать моими AST-узлами без потери всех комментариев, интервалов и так Далее в моем исходном документе.

При создании нового оператора в моем AST я могу легко добавить токены, составляющие этот оператор, в качестве дочерних элементов. Но …

Вопрос:

Если это разумно выполнимо, я хотел бы включить поддержку отображения закорючек. Для этого требуется, чтобы оператор знал, где расположены токены, составляющие оператор. К сожалению, оператор имеет вариации, которые иногда включают больше токенов, иногда меньше токенов. Если бы я создавал AST только для чтения, это не было бы проблемой. Однако я хочу, чтобы мой AST был доступен для чтения и записи в целях рефакторинга! Это означает, что изменение инструкции в AST по существу означает добавление токенов (дочерних элементов инструкции), и, следовательно, класс Statement должен быть способен к повторной обработке самого себя.

Это привело бы к искажению AST с помощью синтаксического анализа кода и больше не поддерживало бы разделение проблем!

Технические подробности:

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

AST-узел для назначений в моем случае в основном будет таким на данный момент:

Иерархический образец AST может быть:

 AssemblyDeclaration
    .. Statement ..
    .. Statement ..
    ClassDeclaration
       .. Token .. // one or more that make up the entire class statement
       .. Token .. // one or more that make up the entire class statement
       Statement(s)
         .. Token .. // Which have their own tokens that make up the statement .. and possibly have sub-nodes of their own such as Expressions which have -their- own Tokens that comprise it.
  

Концептуальная идея базового узла ast, родительского элемента для всего в синтаксическом дереве, независимо от того, является ли это объявлением класса, оператором или токеном.

 public abstract class BaseAstNode : IList<BaseAstNode>
{
    ... implementation of IList<BaseAstNode>
    ... implementation of Visitor Pattern
    ... implementation of Clonable
}    

public sealed class AssignmentStatement : BaseAstNode 
{
public Expression Expression { get; set; }; // Setting this will alter the Tokens (children!) of this node, possibly even ADD Tokens!
public TypeReference Target { get; set; }
}

public sealed class PrimitiveNumberExpression : Expression // is a BaseAstNode
{
public int Value { get; set; } // Setting this will alter the Tokens (children!) of this node!
}

public abstract class Token : BaseAstNode
{
    public Layers Layer { get; set; }
    public TokenType TokenType { get; set; }
    public int Column { get; set; }
    public int Line { get; set; }
    public int Position { get; set; }
    public abstract int Length { get; set; }
    public virtual string Value { get; set; }
    public override string ToString(){}
}
  

Как другие решают эту проблему? Это правильный путь?

Комментарии:

1. Как ваш текущий код создает объекты AssignmentStatement?

2. var stmt = new AssignmentStatement { Выражение = Выражение. Null, Target = ссылка на тип. Null }; .. только затем с выражениями и ссылкой, созданными синтаксическим анализатором.

3. Я предполагаю, что мой вопрос заключается в том, как члены заполняются данными, и почему класс также не может быть заполнен значением start и length?

4. Операторы, являющиеся частью синтаксического дерева, заполняются синтаксическим анализатором. Каждый оператор может легко получить начальный и конечный тег. Но я все еще не мог поместить закорючки под своим, скажем, идентификатором, пока не начал отслеживать все токены, из которых состоит оператор, и -их-позиции.

Ответ №1:

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

В качестве альтернативы, сохраняйте местоположение каждого токена (начало и длину в документе), чтобы вы могли легко получить доступ к положению этого токена, когда вам нужно обратиться к его источнику. Почему бы этого не сделать?

Некоторые возможные варианты оптимизации, если вы можете повторно сгенерировать текст токена из объекта token, заключались бы в том, чтобы не сохранять длину токена или просто искать этот текст в строке, предполагая, что вы хотите делать то же самое с каждым вхождением этого токена в строке (или со всеми строками).

Редактировать: Теперь, когда вы связали токены с исходным кодом, из которого они были проанализированы, вам нужно будет связать инструкции с токенами, из которых они были проанализированы. Есть два способа, которыми вы могли бы это сделать:

  1. Добавьте элемент List<Token> в BaseAstNode. или
  2. Добавьте определенные маркеры-члены в каждый производный класс инструкций, чтобы вы точно знали, какой маркер ссылается на какую часть инструкции.

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

Редактировать 2: Если вы пытаетесь выяснить, как фактический синтаксический анализ будет обрабатывать токены в объекты, производные от BaseAstNode, я еще не рассматривал этот вопрос (было не очень ясно, что это было частью вопроса). Поскольку я не делал этого раньше, мое предложение может оказаться не самым лучшим с первой попытки. Но, надеюсь, это может быть доработано по мере необходимости. Мои первые мысли приводят меня к Системе.Пространство имен Xml, в котором также реализован синтаксический анализатор. Возможно, часть вашего дизайна может быть смоделирована по образцу анализаторов XML и связанных классов. XmlNode, вероятно, похож на ваш класс BaesAstNode. XmlElement, вероятно, аналогичен вашему классу токенов. XmlDocument — это специализация XmlNode, которая обрабатывает весь синтаксический анализ в одной функции loadXML, которая заполняет объект дочерними элементами.

Итак, следуя примеру пространства имен Xml, вы могли бы сделать ParseCode членом ClassDeclaration, который заполняет объект всеми соответствующими дочерними объектами. Он мог бы принимать строку или упорядоченную коллекцию токенов в качестве входных данных и добавлять производные экземпляры BaseAstNode к упорядоченной коллекции объектов BaseAstNode, хранящихся в классе. Но не весь синтаксический анализ мог бы потребоваться в одной функции. Поскольку вы анализируете текст или токены, я предполагаю, что вы начнете с функции ParseCode, помещая проанализированные токены в стек или очередь, пока не проанализируете достаточно токенов, чтобы однозначно определить, какой оператор вы анализируете, затем передайте эти токены в очереди и текущее состояние анализатора соответствующему производному анализатору в одном из классов операторов. Когда производный класс завершит синтаксический анализ токенов, определяющих оператор, токены будут добавлены к оператору, затем вызывающий код добавит проанализированный оператор в родительскую коллекцию операторов.

Комментарии:

1. Я переписал свой вопрос, включив в него запрошенные вами детали и некоторые дополнительные правки для ясности. ЕСЛИ то, что я описал, это то, что вы предлагаете, у меня есть несколько дополнительных вопросов о том, кто отвечает за синтаксический анализ и отслеживание токенов в ast-узлах. Взгляните на новую информацию!

2. Это выглядит довольно близко к тому, о чем я думал. Я, вероятно, не стал бы хранить позицию и столбец независимо; я бы, вероятно, вычислил одно из другого и исходного текста, если бы мне даже понадобились оба. Честно говоря, я раньше этого не делал, но обычно я справляюсь с решением новых проблем :). Итак, какие вопросы остаются… кто отвечал за синтаксический анализ токенов раньше и почему это должно измениться?

3. На самом деле, глядя на это более внимательно, я вроде как подумал, что вы хотели бы отслеживать положение всего (BaseAstNode), а не только токенов. Но если вы думаете, что токены — это единственные вещи, положение которых вас будет волновать, вы могли бы пойти с тем, что вы предложили.

4. Вовсе нет — я действительно хочу отслеживать все. Пробелы, комментарии, языковые элементы. Все это токены. Оператор как таковой будет состоять из пробелов, комментария, конца строк и языковых токенов. Узлы токена (ast) — это узлы с длиной, которые, таким образом, определяют строку и столбец. Что касается синтаксического анализа — если узел оператора должен быть способен понимать свои собственные элементы, он должен проанализировать эту кучу токенов, которые составляют его самого, в действительный оператор, учитывая необязательные ключевые слова, которые могут быть там или нет.

5. Я предполагаю, что у меня возникают трудности с пониманием взаимосвязи между такими объектами, как AssignmentStatement и Token. Является ли AssignmentStatement еще одним уровнем выше по сравнению с token, так что вы сначала разбираете все на токены, а затем разбираете их на классы более высокого уровня, но все они являются производными от BaseAstNode? И нет никакой связи между уровнями? Рассматривали ли вы возможность добавления связей между более высокими уровнями точно так же, как вы только что добавили между токенами и исходным кодом?