J'ai lu plusieurs articles, articles et section 4.1.4, chapitre 4 de Compilers: Principles, Techniques, and Tools (2nd Edition) (aka "The Dragon Book") qui traitent tous du sujet de la récupération d'erreur du compilateur syntaxique. Cependant, après avoir expérimenté avec plusieurs compilateurs modernes, j'ai vu qu'ils récupèrent également des erreurs sémantiques , ainsi que des erreurs syntaxiques.
Je comprends assez bien les algorithmes et techniques derrière les compilateurs récupérant des erreurs liées syntaxiquement, mais je ne comprends pas exactement comment un compilateur peut récupérer d'une erreur sémantique.
J'utilise actuellement une légère variation du modèle de visiteur pour générer du code à partir de mon arbre de syntaxe abstraite. Considérez mon compilateur qui compile les expressions suivantes:
1 / (2 * (3 + "4"))
Le compilateur générerait l'arborescence de syntaxe abstraite suivante:
op(/)
|
-------
/ \
int(1) op(*)
|
-------
/ \
int(2) op(+)
|
-------
/ \
int(3) str(4)
La phase de génération de code utiliserait alors le modèle de visiteur pour parcourir récursivement l'arborescence de syntaxe abstraite et effectuer une vérification de type. L'arbre de syntaxe abstraite serait parcouru jusqu'à ce que le compilateur atteigne la partie la plus interne de l'expression; (3 + "4")
. Le compilateur vérifie ensuite chaque côté des expressions et constate qu'elles ne sont pas sémantiquement équivalentes. Le compilateur déclenche une erreur de type. Voici où réside le problème. Que doit faire maintenant le compilateur ?
Pour que le compilateur se remette de cette erreur et continue de vérifier le type des parties externes des expressions, il devrait renvoyer un type ( int
ou str
) d'évaluation de la partie la plus interne de l'expression à la partie la plus interne suivante de l'expression. Mais il n'a tout simplement pas de type à renvoyer . Puisqu'une erreur de type s'est produite, aucun type n'a été déduit.
Une solution possible que j'ai postulée, est que si une erreur de type se produit, une erreur doit être déclenchée et une valeur spéciale qui signifie qu'une erreur de type s'est produite doit être renvoyée aux appels précédents de traversée de l'arbre de syntaxe abstraite. Si les appels de parcours précédents rencontrent cette valeur, ils savent qu'une erreur de type s'est produite plus profondément dans l'arbre de syntaxe abstraite et doivent éviter d'essayer de déduire un type. Bien que cette méthode semble fonctionner, elle semble être très inefficace. Si la partie la plus à l'intérieur d'une expression est au fond de l'arbre de syntaxe abstraite, alors le compilateur devra effectuer de nombreux appels récursifs uniquement pour se rendre compte qu'aucun travail réel ne peut être effectué, et simplement revenir de chacun.
Est-ce que la méthode que j'ai décrite ci-dessus est utilisée (j'en doute). Si oui, n'est-ce pas efficace? Sinon, quelles sont exactement les méthodes utilisées lorsque les compilateurs récupèrent des erreurs sémantiques?
la source
Réponses:
L'idée que vous proposez est essentiellement correcte.
La clé est que le type d'un nœud AST est calculé une seule fois puis stocké. Chaque fois que le type est à nouveau nécessaire, il récupère simplement le type stocké. Si la résolution se termine par une erreur, un type d'erreur est stocké à la place.
la source
Une approche intéressante consiste à avoir un type spécial pour les erreurs. Lorsqu'une telle erreur est rencontrée pour la première fois, un diagnostic est enregistré et le type d'erreur est renvoyé en tant que type de l'expression. Ce type d'erreur a des propriétés intéressantes:
Avec cette combinaison, vous pouvez réellement compiler avec succès du code qui contient des erreurs de type et tant que ce code n'est pas réellement utilisé, aucune erreur d'exécution ne se produit. Cela peut être utile, par exemple, pour vous permettre d'exécuter des tests unitaires pour les parties du code qui ne sont pas affectées.
la source
S'il y a une erreur sémantique, un message d'erreur de compilation l'indiquant est émis à l'utilisateur.
Une fois cela fait, il est possible d'interrompre la compilation car le programme d'entrée est en erreur - ce n'est pas un programme légal dans la langue, il peut donc simplement être rejeté.
C'est assez dur, cependant, il existe donc des alternatives plus douces. Abandonnez toute génération de code et génération de fichier de sortie, mais continuez quelque chose pour rechercher plus d'erreurs.
Par exemple, il peut simplement abandonner toute analyse de type supplémentaire pour l'arborescence d'expression actuelle et continuer à traiter les expressions des instructions suivantes.
la source
Supposons que votre langage autorise l'ajout d'entiers et la concaténation de chaînes avec l'
+
opérateur.Étant donné que
int + string
n'est pas autorisé, l'évaluation de la+
entraînera une erreur signalée. Le compilateur pourrait simplement retourner enerror
tant que type. Ou il peut être plus intelligent, carint + int -> int
etstring + string -> string
sont autorisés, il peut retourner "erreur, peut être int ou chaîne".Vient ensuite l'
*
opérateur, et nous supposerons que seulint + int
est autorisé. Le compilateur peut alors décider que le+
fait était censé retournerint
, et le type renvoyé pour le*
serait alorsint
, sans aucun message d'erreur.la source