Les cartes source JavaScripts ne semblent généralement pas plus fines que la granularité token. Par exemple, la carte d'identité utilise la granularité des jetons .
Je sais que j'ai vu d'autres exemples, mais je ne me souviens pas où.
Pourquoi n'utilisons-nous pas plutôt la granularité basée sur les nœuds AST? Autrement dit, si nos cartes sources avaient des emplacements pour tous et seulement des débuts de AST nœuds, quel serait l'inconvénient?
À ma connaissance, les cartes sources sont utilisées pour le décodage de la pile de crash et pour le débogage: il n'y aura jamais d'emplacement d'erreur ou de point d'arrêt utile qui ne se trouve pas au début d'un nœud AST, non?
Quelques précisions supplémentaires:
La question concerne les cas où le AST est déjà connu. Donc "il est plus cher de générer un AST qu'un tableau de jetons") ne répondrait pas à la question.
L'impact pratique de cette question est que si nous pouvions réduire la granularité des cartes sources tout en préservant le comportement des débogueurs et des décodeurs de pile de crash, les cartes sources pourraient être beaucoup plus petites. Le principal avantage étant les performances des débogueurs: les outils de développement peuvent prendre beaucoup de temps pour traiter de gros fichiers source, ce qui rend le débogage difficile.
Voici un exemple d'ajout d'emplacements de carte source au niveau du jeton à l'aide de la bibliothèque source-map :
for (const token of tokens) {
generator.addMapping({
source: "source.js",
original: token.location(),
generated: generated.get(token).location(),
});
}
Et voici un exemple d'ajout d'emplacements au niveau du nœud AST:
for (const node of nodes) {
generator.addMapping({
source: "source.js",
original: node.location(),
generated: generated.get(node).location(),
});
}
Q1: Pourquoi espérer qu'il y aura moins de démarrages de AST nœuds que de démarrages de jetons?
A1: Parce que s'il y avait plus de démarrages de AST nœuds que de démarrages de jetons, alors il y aurait un AST Node qui commence à un non-jeton. Ce qui serait tout un exploit pour l'auteur de l'analyseur! Pour rendre cela concret, supposons que vous ayez l'instruction JavaScript suivante:
const a = function *() { return a + ++ b }
Voici les emplacements au début des jetons:
const a = function *() { return a + ++ b } /*
^ ^ ^ ^^^ ^ ^ ^ ^ ^ ^ ^
*/
Voici à peu près où la plupart des analyseurs diront les débuts de AST Nodes are.
const a = function *() { return a + ++ b } /*
^ ^ ^ ^ ^ ^ ^
*/
C'est une réduction de 46% du nombre d'emplacements de la carte source!
Q2: Pourquoi s'attendre à ce que les cartes sources de granularité AST-Node soient plus petites?
A2: Voir A1 ci-dessus
Q3: Quel format utiliseriez-vous pour référencer AST Nodes?
A3: Aucun format. Voir l'exemple de code dans Update 1 ci-dessus. Je parle d'ajouter des emplacements de carte source pour les débuts de AST Nodes. Le processus est presque exactement le même que le processus pour ajouter des emplacements de carte source pour les débuts de jetons, sauf que vous ajoutez moins Emplacements.
Q4: Comment pouvez-vous affirmer que tous les outils traitant de la carte source utilisent la même représentation AST?
A4: Supposons que nous contrôlons l'ensemble du pipeline et utilisons le même analyseur partout.
Il est possible d'utiliser AST granularité, mais généralement pour construire un AST, vous devez de toute façon avant de tokeniser le code. À des fins de débogage AST est une étape inutile) car l'analyseur de syntaxe doit être alimenté en données tokenisées pour fonctionner.
Une ressource intéressante sur le sujet
Je suggère également d'explorer code source acornJS et de voir comment il produit AST
Le compilateur TypeScript
n'émet en fait que des emplacements de carte source sur AST, à quelques exceptions près pour améliorer la compatibilité avec certains outils qui attendent des mappages pour certaines positions, donc les cartes basées sur des jetons ne sont pas réellement pas tout à fait universel. Dans l'exemple que vous donnez, les sourcemaps de TS sont pour des positions comme ceci:
const a = function *() { return a + ++ b } /*
^ ^^ ^ ^ ^^ ^ ^^^
*/
Qui sont généralement à la fois le début et la fin de chaque identifiant AST (plus commence autrement).
La justification du mappage des positions de début et pour un identifiant AST est assez simple - lorsque vous renommez un identifiant, vous voulez qu'une plage de sélection sur cet identifiant renommé soit pouvoir mapper à l'identifiant d'origine, sans nécessairement s'appuyer sur des heuristiques.