ОБНОВЛЕНИЕ: Этот вопрос был the subject of my blog on June 8th, 2012. Спасибо за большой вопрос!
Большой вопрос. Мы обсуждали вопросы, которые вы поднимаете в течение долгого времени.
Мы хотели бы иметь структуру данных, которая имеет следующие характеристики:
- Неизменный.
- Форма дерева.
- Недорогой доступ к родительским узлам из дочерних узлов.
- Возможна карта с узла в дереве до смещения символа в тексте.
- Постоянный.
По настойчивости Я имею в виду способность к повторному использованию большинства существующих узлов в дереве, когда редактирование производится в текстовом буфер. Поскольку узлы являются неизменными, нет никаких препятствий для их повторного использования. Нам это нужно для производительности; мы не можем повторно разыгрывать огромные искажения файла каждый раз, когда вы нажимаете клавишу. Нам нужно повторно lex и повторно проанализировать только части дерева, на которые повлияло редактирование.
Теперь, когда вы пытаетесь поставить все пять из этих вещей в одну структуру данных, вы сразу же столкнулись с проблемами:
- Как построить узел в первую очередь? Родитель и ребенок оба ссылаются друг на друга и неизменны, поэтому кто-то строится первым?
- Предположим, вам удастся решить эту проблему: как вы это делаете? Вы не можете повторно использовать дочерний узел в другом родителе, потому что это связано с сообщением ребенку о том, что у него есть новый родитель. Но ребенок неизменен.
- Предположим, что вам удастся решить эту проблему: когда вы вставляете новый символ в буфер редактирования, абсолютное положение каждого узла, который отображается после позиции. Это затрудняет создание постоянной структуры данных, поскольку любое редактирование может изменять интервалы большинства узлов!
Но в команде Roslyn мы обычно делаем невозможные вещи. На самом деле мы делаем невозможное, сохраняя два дерева обработки. «Зеленое» дерево неизменно, постоянно, не имеет родительских ссылок, построено «снизу вверх», и каждый узел отслеживает его шириной, но не его абсолютное положение. Когда происходит редактирование, мы восстанавливаем только части зеленого дерева, на которые повлияло редактирование, которое обычно составляет около O (log n) общих узлов синтаксического анализа в дереве.
«красное» дерево является неизменным фасад, который построен вокруг зеленого дерева; он построен «сверху вниз» по запросу и выброшен на каждое редактирование. Он вычисляет родительские ссылки на , производя их по требованию, когда вы спускаетесь через дерево сверху. Он производит абсолютные позиции, вычисляя их по ширине, опять же, когда вы спускаетесь.
Вы, пользователь, только увидите красное дерево; зеленое дерево - это деталь реализации. Если вы входите во внутреннее состояние узла синтаксического анализа, вы фактически увидите, что есть ссылка на другой узел, где он разный; это узел зеленого дерева.
Кстати, они называются «красными/зелеными деревьями», потому что это были цвета маркеров доски, которые мы использовали для создания структуры данных на проектной встрече. Для цветов нет другого смысла.
Преимущество этой стратегии в том, что мы получаем все те великие вещи: неизменность, настойчивость, родительские ссылки и т. Д. Стоимость заключается в том, что эта система сложна и может потреблять много памяти, если «красные» фасады становятся большими. В настоящее время мы проводим эксперименты, чтобы выяснить, можем ли мы сократить некоторые из расходов без потери преимуществ.
И для рассмотрения части вашего вопроса об IProjects и IDocuments: мы используем аналогичную модель на уровне сервисов. Внутренне существуют типы DocumentState и ProjectState, которые морально эквивалентны зеленым узлам синтаксического дерева. Объектами IProject/IDocument, которые вы получаете, являются фасады красного узла для них. Если вы посмотрите на реализацию Roslyn.Services.Project в декомпиляторе, вы увидите, что почти все вызовы переходят к внутренним объектам состояния. –
@ Эрик сожалеет о замечании, но вы противоречите себе. «Расходы и сложность построения сложной постоянной структуры данных не оплачиваются сами». Ref: http://stackoverflow.com/questions/6742923/if-strings-are-immutable-in-net-then-why- do-substring-take-on-time/6750591 # 6750591 Если у вас были высокие показатели производительности, почему вы сделали его неизменным в первую очередь? Есть только другая причина, кроме очевидных? например проще сделать threadafe, рассуждать о и т. д. –
@ lukas Вы принимаете эту цитату из контекста. Предыдущее предложение было «Потому что, когда вы смотрите на операции, которые обычно выполняются в строках в .NET-программах, по-любому вряд ли хуже всего просто создать совершенно новую строку». OTOH, когда вы смотрите на операции, которые обычно выполняются в дереве выражений - например, ввод нескольких символов в исходный файл - значительно хуже построить совершенно новое дерево выражений. Поэтому они только строят половину. – Timbo