Je vote pour 2 ou 3, avec une préférence pour 3 (voir ci-dessous les raisons de ne pas choisir 1).
Pour (2), vous pourriez commencer par une interface ASTNode
interface ASTNode
{
List<ASTNode> children();
}
Vous pourriez alors créer des sous-types pour des types spécifiques de nœuds syntaxiques, ou ajouter des collections génériques à l'interface pour récupérer des attributs.
Vous mettez en œuvre des instances de cette interface, par exemple JapaASTNode
qui englobe le nœud AST de la bibliothèque. L'inconvénient de cette méthode est que vous finissez par créer un grand nombre de wrappers, et leur gestion peut être délicate, par exemple la gestion du mappage de vos wrappers vers les AST sous-jacents. Certains clients peuvent s'attendre à recevoir le même wrapper pour le même AST, plutôt que de nouveaux wrappers à chaque fois que le même AST est utilisé. Ce problème peut être résolu en utilisant un WeakHashMap, qui garde la trace du wrapper utilisé pour chaque AST, sans retenir l'AST plus longtemps que nécessaire.
Un exemple concret de ce schéma se trouve dans dom4j - Les interfaces sont utilisées pour envelopper les objets DOM de différents analyseurs XML, cachant le reste du système de l'implémentation spécifique de DOM.
L'autre alternative (3) est de ne pas envelopper les objets, mais de fournir des méthodes de navigation qui savent comment trouver les objets liés (par exemple, les ndoes enfants) et extraire des informations, par exemple, obtenir les tokens associés au noeud AST.
Pour ce faire, on dispose d'une interface Navigator qui prend les AST (dans leur forme originale à partir de la bibliothèque) et sait comment les traiter, par ex.
interface Navigator
{
List<Object> getChildren(Object ast);
List<Object> getTokens();
}
qui pourrait être mis en œuvre comme suit
class JapaNavigator implements Navigator
{
List<Object> getChildren(Object ast) {
JapaAST japaAST = (JapaAST)ast;
return japaAST.getChildren();
};
// for illustration - not the actual japa api
}
Ça a l'air un peu moche avec le moulage, mais n'oubliez pas que c'est interne.
Cela fonctionne bien si le modèle objet des bibliothèques est similaire, ce qui peut raisonnablement être le cas pour les AST, et cela vous évite d'avoir à envelopper absolument tout. L'inconvénient, c'est que vous travaillez avec Object tout le temps en externe. Cependant, il y a peu de danger de l'utiliser de la mauvaise façon - l'implémentation spécifique de Navigator coule ces objets dans leur type AST attendu, et donc les erreurs sont rapidement découvertes (par exemple, passer un objet qui est un token à une méthode qui attend un AST).
Pour un exemple concret, voir jaxen qui applique avec succès xpath sur un certain nombre de modèles d'objets différents, de la même manière que vous voudriez parcourir différents AST.
EDIT :
Bien que (1) fonctionne, vous devrez coder un grand nombre d'itérateurs, de convertisseurs et de divers objets de modèle AST, pour construire votre modèle indépendant de l'analyseur, ce qui finira par représenter une quantité importante de code. (Par exemple, dupliquer tous les nœuds, dupliquer tous les tokens, les visiteurs pour gérer les différents types de nœuds). Ensuite, vous devrez dupliquer la plupart de ces éléments si vous décidez de passer à un autre parseur. Bien sûr, vous pourriez éviter de dupliquer le constructeur pour différents analyseurs en implémentant une interface neutre pour l'analyseur en utilisant (2) ou (3) ci-dessus, et en l'utilisant comme base de votre copie, mais c'est l'équivalent en programmation de courir après votre queue !