MISE À JOUR: Cette question a fait l'objet de mon blog le 8 juin 2012 . Merci pour la bonne question!
Excellente question. Nous avons débattu des questions que vous soulevez pendant très, très longtemps.
Nous aimerions avoir une structure de données qui présente les caractéristiques suivantes:
- Immuable.
- La forme d'un arbre.
- Accès bon marché aux nœuds parents à partir des nœuds enfants.
- Possibilité de mapper d'un nœud dans l'arborescence vers un décalage de caractère dans le texte.
- Persistant .
Par persistance, j'entends la possibilité de réutiliser la plupart des nœuds existants dans l'arborescence lorsqu'une modification est apportée au tampon de texte. Étant donné que les nœuds sont immuables, il n'y a aucun obstacle à leur réutilisation. Nous en avons besoin pour la performance; nous ne pouvons pas ré-analyser d'énormes wodges du fichier chaque fois que vous appuyez sur une touche. Nous devons re-lex et ré-analyser uniquement les parties de l'arbre qui ont été affectées par l'édition.
Maintenant, lorsque vous essayez de mettre ces cinq éléments dans une seule structure de données, vous rencontrez immédiatement des problèmes:
- Comment construisez-vous un nœud en premier lieu? Le parent et l'enfant se réfèrent l'un à l'autre et sont immuables, alors lequel est construit en premier?
- Supposons que vous parveniez à résoudre ce problème: comment le rendre persistant? Vous ne pouvez pas réutiliser un nœud enfant dans un parent différent, car cela impliquerait d'indiquer à l'enfant qu'il a un nouveau parent. Mais l'enfant est immuable.
- Supposons que vous parveniez à résoudre ce problème: lorsque vous insérez un nouveau caractère dans le tampon d'édition, la position absolue de chaque nœud qui est mappé à une position après ce point change. Cela rend très difficile la création d'une structure de données persistante, car toute modification peut modifier les étendues de la plupart des nœuds!
Mais dans l'équipe de Roslyn, nous faisons régulièrement des choses impossibles. Nous faisons en fait l'impossible en conservant deux arbres d'analyse. L'arbre "vert" est immuable, persistant, n'a pas de références parentes, est construit "de bas en haut", et chaque nœud suit sa largeur mais pas sa position absolue . Lorsqu'une modification se produit, nous ne reconstruisons que les parties de l'arbre vert qui ont été affectées par la modification, ce qui correspond généralement à O (log n) du total des nœuds d'analyse dans l'arborescence.
L'arbre «rouge» est une façade immuable qui est construite autour de l'arbre vert; il est construit "de haut en bas" à la demande et jeté à chaque édition. Il calcule les références parentes en les fabriquant à la demande lorsque vous descendez dans l'arborescence depuis le haut . Il fabrique des positions absolues en les calculant à partir des largeurs, encore une fois, lorsque vous descendez.
Vous, l'utilisateur, ne voyez que l'arbre rouge; l'arbre vert est un détail d'implémentation. Si vous examinez l'état interne d'un nœud d'analyse, vous verrez en fait qu'il y a une référence à un autre nœud d'analyse d'un type différent; c'est le nœud de l'arbre vert.
Incidemment, on les appelle «arbres rouges / verts» parce que ce sont les couleurs des marqueurs du tableau blanc que nous avons utilisées pour dessiner la structure des données lors de la réunion de conception. Il n'y a pas d'autre signification aux couleurs.
L'avantage de cette stratégie est que nous obtenons toutes ces grandes choses: l'immuabilité, la persistance, les références parentales, etc. Le coût est que ce système est complexe et peut consommer beaucoup de mémoire si les façades «rouges» deviennent grandes. Nous faisons actuellement des expériences pour voir si nous pouvons réduire certains des coûts sans perdre les avantages.