• Aucun résultat trouvé

Mode trace

Dans le document Apprendre et enseigner Prolog pdf (Page 148-160)

Exemple 3 : Cet exemple montre comment utiliser une variable pour contrôler un programme de l'extérieur La règle liste_de_un qui a été donnée

6. L'environnement 1 Comment sortir de Prolog

6.6. Outil de mise au point de programmes

6.6.1. Mode trace

L'outil de mise au point mixe deux modes de fonctionnement : • un mode trace, où il visualise l'exécution,

• un mode interactif, où il permet de prendre le contrôle au cours de l'exécution et d'effectuer des actions.

Dans un premier temps, si l'on ne veut pas se plonger dans le détail des commandes du mode interactif, le mode trace permet d'afficher toutes les informations nécessaires à la compréhension du programme.

Le mode trace est donc une approche simple et immédiate pour se rendre compte de l'exécution d'un programme.

Chaque but effacé est affiché avec ses arguments unifiés sur la règle choisie pour l'effacement courant. Les backtracking sont annoncés en précisant le prédicat pour lequel il restait des choix et le rang dans le paquet de la nouvelle règle.

Si le programme d'exemple menu.p2 est chargé et si le mode trace est actif, la séquence suivante montre la présentation de ces informations:

> repas(e,Chapon_farci,d); hors_d_oeuvre( Artichauts_Melanie) plat( Chapon_farci) RECALL(2): plat / 1 plat( Chapon_farci) poisson( Chapon_farci) dessert( Sorbet_aux_poires) {e=Artichauts_Melanie,d=Sorbet_aux_poires} RECALL(2): dessert / 1 dessert( Fraises_chantilly) {e=Artichauts_Melanie,d=Fraises_chantilly} RECALL(3): dessert / 1 dessert( Melon_en_surprise) {e=Artichauts_Melanie,d=Melon_en_surprise} …

Pour faciliter la lisibilité (pour des programmes qui font eux-mêmes des écritures sur la sortie courante), les informations fournies par la trace peuvent être redirigées sur un fichier.

trace

Active la trace. trace(f)

Active la trace. Redirigera toutes les impressions de la trace dans le fichier de nom f (chaîne Prolog). Le fichier est fermé à l'exécution d'un prédicat de désactivation de l'outil de mise au point. Si les impressions de mise au point étaient déjà redirigées, le précédent fichier est fermé pour être remplacé par f. no_trace

Désactive la trace. 6.6.2. Mode interactif

Si la trace est simple à activer, elle peut rapidement donner un flot d'informations important, où il serait difficile de se repérer. A ce moment là, le mode interactif devient d'une grande utilité puisqu'il permet d'aller directement à l'information importante. Ses atouts sont :

• les commandes de progression dans le code, • le paramétrage de l'information à produire, • les commandes annexes sur l'exécution.

Le mode interactif permet de choisir les informations qu'on veut extraire du programme, dans le format approprié, sans modifier le programme, et seulement dans les portions de code désignées. Il permet surtout, de prendre le contrôle où l'on souhaite, pour lancer des actions.

6.6.2.1. Points d'arrêt

En mode interactif, le debugger va proposer sa banière d'invite (DBG) et attendre une commande sur chaque point d'arrêt. L'important pour avoir accès à l'interpréteur de commande est de bien définir les points d'arrêt. On en distingue deux types:

• les points d'arrêt fixes: ils subsistent tant que le mode debug est actif et tant qu'ils ne sont pas désactivés par commande;

• les points d'arrêt momentanés: ils ne sont valides que le temps d'une commande, qui définit en fait le prochain point de contrôle du debugger. Un point d'arrêt se définit sur un prédicat, le debugger s'arrête ensuite dès que le programme courant fait appel à ce prédicat. L'arrêt de la machine Prolog se fera donc au moment où le but doit être effacé, avant que l'unification avec la tête de la règle choisie soit faite.

Des commandes spécifiques, ainsi que des prédicats prédéfinis, permettent de définir les points d'arrêt fixes. Les points d'arrêt momentanés sont définis

Quand la machine Prolog tourne, avec le debugger actif, une interruption utilisateur poste un point d'arrêt momentané sur le but courant et le mode interactif est réactivé. Les interruptions utilisateur sont ainsi interceptées et ne produisent pas d'erreur. Voir les commandes du debugger +, -, b et les règles prédéfinies spy, no_spy, show_spy.

6.6.2.2. Progression dans le code

La progression dans le code se fait de point d'arrêt en point d'arrêt.

Trois granularités de progression sont possibles. On les utilisera en fonction de la "proximité" de l'erreur, ou du type d'information que l'on veut extraire d'une partie du programme.

Les progressions possibles, à partir d'un point d'arrêt, sont:

1er cas: on connait l'endroit du programme qui fait défaut, on veut y aller directement et rapidement.

Réponse: (go to spy) aller jusqu'au prochain point d'arrêt fixe, qui peut se trouver n'importe où dans le code.

2ème cas: on veut voir "de près" l'exécution de la fonction, sans descendre au niveau bas de ses "sous-programmes".

Réponse: (next) aller jusqu'au prochain but de même niveau ou de niveau supérieur. On ne s'intéresse pas aux niveaux1 inférieurs, c'est à dire que l'on

ignore le corps du but courant.

3ème cas: on veut voir exactement toute l'exécution de la fonction, sans oublier aucune instruction.

Schématisons sur notre exemple de menu, les progressions possibles par next et step : repas hors_d_oeuvre plat viande dessert ... step next

On remarquera que sur les assertions, step et next ont le même effet.

Les commandes de progression dans le code permettent de définir quel doit être le prochain arrêt et quelles informations doivent être obtenues entre temps. En effet, à chaque progression, on pourra choisir ou pas d'afficher les informations sur l'exécution de la portion de programme.

Voir les commandes du debugger RC, n, N, g, t. 6.6.2.3. Terminer l'exécution

Ayant suffisamment dégrossi une partie du programme, il faut maintenant le terminer. Il est possible de :

- (go to end) continuer jusqu'à la fin du programme : en imprimant toutes les informations, en imprimant seulement celles concernant les points d'arrêt fixes, en quittant le mode debug et continuant normalement.

- (abort) interrompre le programme. - (quit) quitter Prolog.

Voir les commandes du debugger a, e, G, T, q. 6.6.2.4. Affichage des informations

Nous allons décrire maintenant à quels moments se font les affichages et sous quelle forme.

Quand?

Un Backtracking est généré

L'information est affichée. Elle indique que c'est une alternative (RECALL), le numéro

de la nouvelle règle choisie et le but qu'on essaie à nouveau d'effacer. Le but à réessayer fait partie des buts à effacer, il peut donc être utilisé comme point d'arrêt. Par exemple, sur l'éxécution :

> plat(Chapon_farci); CALL: plat( Chapon_farci) DBG:

CALL: viande( Chapon_farci) DBG:

RECALL(2): plat( Chapon_farci) DBG:

CALL: poisson( Chapon_farci) DBG:

{}

L'information sur le backtracking est :

RECALL(2): plat( Chapon_farci)

Le numéro de la règle à réessayer est noté entre parenthèse (2). Un point d'arrêt est

mis au nouvel appel du but, sur :

plat( Chapon_farci)

On a alors accès, sur cette autre alternative, à toutes les possibilités offertes par l'outil de mise au point.

Autre exemple, si un point d'arrêt fixe est mis sur plat/1, le debugger s'arrête sur toutes ses alternatives :

> repas(e,Chapon_farci,d);

CALL: repas( v147, Chapon_farci, v284)

DBG: +plat/1 (ajoute le point d'arrêt)

DBG: g (go to spy)

CALL: plat( Chapon_farci) DBG: g

RECALL(2): plat( Chapon_farci) DBG: g

{e=Artichauts_Melanie,d=Sorbet_aux_poires} {e=Artichauts_Melanie,d=Fraises_chantilly} {e=Artichauts_Melanie,d=Melon_en_surprise} CALL: plat( Chapon_farci)

DBG: g

RECALL(2): plat( Chapon_farci) DBG: g

Une erreur est générée par la machine ou par un prédicat externe

titi(y) -> string_ident(y,z) outl(z); ; {} > debug toto(x,12); CALL: toto( v156, 12) DBG: block CALL: titi( 12) DBG: CALL: string_ident( 12, v360) DBG:

ARGUMENT DE MAUVAIS TYPE

^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^ ^^^^^^^^ titi / 1, rule number 1 ^^^^^^^^ toto / 2, rule number 1 ^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^ CALL: outl( 253)

DBG:

L'information sur l'erreur est :

ARGUMENT DE MAUVAIS TYPE

^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^ ^^^^^^^^ titi / 1, rule number 1 ^^^^^^^^ toto / 2, rule number 1 ^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^

Il est possible de supprimer par commande, pour une session de mise au point, l'affichage de ces informations sur les erreurs. Voir la commande du debugger s. Les erreurs produites en Prolog par block_exit ne sont pas mises en évidence par ce mécanisme. La règle prédéfinie block_exit, est traitée comme un prédicat ordinaire. Pour être averti de ce type d'erreur, il suffit de mettre un point d'arrêt sur block_exit/1 et block_exit/2.

Sur un point d'arrêt, c'est à dire quand un but est appelé

L'impression des buts peut se faire à deux moments, nous les appellerons avant unification et après unification. Voir les commandes du debugger p, P.

Les buts peuvent être imprimés avant l'unification, c'est-à-dire l'impression montre le but tel qu'il apparait dans la résolvante avant son unification avec la tête de la règle choisie pour son effacement.

Ils peuvent être imprimés après unification, c'est-à-dire l'impression a lieu après le choix de la règle et juste avant le prochain point d'arrêt. Par conséquent, on pourra avoir des résultats différents d'une exécution à l'autre pour des progressions de pas différents. Par exemple pour un but donné, si le pas de progression sous le debugger est celui d'un step, la machine Prolog aura simplement unifié le but avec la tête de la règle. Alors qu'après un pas tel que next, la machine Prolog aura exécuté le corps de la règle où des unifications se seront passées, et les arguments du but n'auront pas forcément les mêmes valeurs que précédemment.

Voyons sur notre exemple, la valeur des arguments après unification, dans les deux cas de progression :

> repas(e,p,d);

CALL: repas( v156, v191, v226)

DBG: P1 (valide l'impression après unification) DBG: CALL: hors_d_oeuvre( v156) DBG: hors_d_oeuvre( Artichauts_Melanie) CALL: plat( v191) DBG: plat( v191) CALL: viande( v191) DBG: viande( Grillade_de_boeuf) CALL: dessert( v226) DBG: g (go to spy) {e=Artichauts_Melanie,p=Grillade_de_boeuf,d=Sorbet_aux_poires } … > repas(e,p,d); CALL: repas( v156, v191, v226) DBG: P1 DBG: CALL: hors_d_oeuvre( v156) DBG: hors_d_oeuvre( Artichauts_Melanie) CALL: plat( v191) DBG: n (next) plat( Grillade_de_boeuf) CALL: dessert( v226) DBG: g {e=Artichauts_Melanie,p=Grillade_de_boeuf,d=Sorbet_aux_poires } … Comment? Présentation

D'une manière générale pour faciliter la lisibilité de la mise au point et différencier visuellement les messages du programme et ceux du debugger, il est possible de définir une marge (en nombre de caractères) pour les impressions du debugger. Quelque soit le message transmis par le debugger, il sera indenté du nombre de caractères de la marge, pour l'affichage.

> repas(e,p,d); CALL: repas( v156, v191, v226) DBG: CALL: hors_d_oeuvre( v156) DBG: CALL: plat( v191) DBG: n (next)

DBG: {e=Artichauts_Melanie,p=Grillade_de_boeuf,d=Fraises_chantilly } RECALL(3): dessert(v190) DBG: {e=Artichauts_Melanie,p=Grillade_de_boeuf,d=Melon_en_surprise } RECALL(2): viande(v165) DBG: CALL: dessert( v226) DBG:

Une autre alternative est de rediriger les messages du debugger dans un fichier. Dans ce cas, seule la séquence d'invite apparaîtra dans l'unité de trace.

Voir la commande du debugger i, ou les règles prédéfinies debug(f) et debug(n,f). Précision

La précision de l'affichage des buts est paramétrable, selon la quantité d'informations que l'on souhaite obtenir. Trois modes sont disponibles :

- impression des prédicats sans leurs arguments éventuels mais précisant l'arité, sous la forme identificateur/arité.

- impression des prédicats avec leurs arguments éventuels, en notation fonctionnelle avec une limite en profondeur paramétrable. A partir d'une profondeur de 1000, la totalité du terme est imprimé. L'impression des arbres infinis en totalité utilise sa représentation en système d'équations. On ne sait pas dire à priori si l'impression restreinte d'un arbre infini est possible.

- impression des prédicats avec leurs arguments éventuels, sous forme d'arbre par le prédicat Dessin:draw_tree. Le module de dessin d'arbre doit être chargé pour pouvoir utiliser ce mode. Il est possible de modifier la règle Dessin:draw_tree, de manière à redéfinir ce qui est imprimé par le debugger.

Détail de la notation fonctionnelle en profondeur limitée :

On attribue une profondeur à chaque atome du terme à imprimer. Ne seront affichés que les atomes dont la profondeur est inférieure à celle fixée.

Le comptage se fait récursivement à partir du but à afficher, il démarre à 1. La profondeur est incrémentée lorsqu'on passe d'un terme à un sous terme de la manière suivante :

Si un terme fonctionnel f(a1, t2, …, as) apparaît dans un terme de profondeur n alors le foncteur f est de profondeur n, et ses arguments a1, …, as sont de profondeur n+1.

Si un n-uplet <t1, t2, …, ts> (t1 n'est pas un identificateur) apparaît dans un terme de profondeur n alors l'atome <> est de profondeur n, et les arguments du n-uplet t1, t2, …, ts sont de profondeur n+1.

Si une liste x.y apparaît dans un terme de profondeur n, alors x est de profondeur n et y est de profondeur n+1.

Par exemple, voici comment sont définies les profondeurs des atomes pour le terme suivant :

element_de( 12, 1. 2. 3. 4. 5. nil, non)

1 2 2 3 4 5 6 7 2

En limite de profondeur 3, il sera imprimé : element_de( 12, [ 1, 2,…], non); Voir la commande du debugger m.

Choix de l'information

Certaines parties du programme ont déjà été prouvées, il n'est pas nécessaire de les vérifier. En mode interactif, par la progression next, on peut éviter de développer le corps d'un prédicat. En mode trace (trace totale ou impression des informations entre deux points d'arrêt) la même possibilité est offerte.

Par exemple, si plat/1 ne doit pas être développé, on obtient : > trace repas(e,p,d) !; hors_d_oeuvre( Artichauts_Melanie) plat( Grillade_de_boeuf) dessert( Sorbet_aux_poires) {e=Artichauts_Melanie,p=Grillade_de_boeuf,d=Sorbet_aux_poires } … au lieu de : > repas(e,p,d) !; hors_d_oeuvre( Artichauts_Melanie) plat( v191) viande( Grillade_de_boeuf) dessert( Sorbet_aux_poires) {e=Artichauts_Melanie,p=Grillade_de_boeuf,d=Sorbet_aux_poires } …

Voir les commandes du debugger >, <, j. 6.6.2.5. Informations annexes sur l'exécution

Pour détailler l'état du programme en cours d'exécution, sur un but particulier, on peut se poser les questions : où en est-on dans l'exécution du programme? Et quelle règle essaie-t-on d'effacer pour ça?

Quelle est la branche courante de l'arbre de résolution?

^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^ ^^^^^^^^ titi / 1, rule number 1 ^^^^^^^^ toto / 2, rule number 1 ^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^

Quelle est la position du but dans le programme?

---> repas / 3, rule number 1, goal number 3 in queue

En fait, la branche courante de la résolution est donnée par l'état de la pile de récursion. L'optimisation de l'appel terminal pratiquée par la machine Prolog, ne permet pas de visualiser les règles pour lesquelles le but en cours d'exécution est le dernier de la queue. Par exemple :

> repas(e,p,d);

CALL: repas( v156, v191, v226)

DBG: s ici la pile est vide, puisque repas n'est pas encore installé. ^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^

^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^ DBG:

CALL: hors_d_oeuvre( v156)

DBG: s ici elle contient repas, puisque c'est le but appelant. ^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^

^^^^^^^^ repas / 3, rule number 1 ^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^ DBG:

CALL: plat( v191)

DBG: s ici aussi, puisque plat et hors_d_oeuvre sont de même profondeur.

^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^ ^^^^^^^^ repas / 3, rule number 1 ^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^ DBG:

CALL: viande( v191)

DBG: s ici plat n'apparait pas puisque viande est son appel terminal. ^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^

^^^^^^^^ repas / 3, rule number 1 ^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^ DBG:

CALL: dessert( v226)

DBG: s ici repas n'apparait plus puisque dessert est son appel terminal.

^^^^^^^^^^^^^^^^TOP^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^BOTTOM^^^^^^^^^^^^^^^^ DBG:

Enfin, pour avoir accès au plus grand nombre d'informations, il est possible d'ouvrir une session Prolog. Ainsi, il est possible de vérifier les effets de bords du programme, de relancer une partie du programme, de lancer un programme annexe, de vérifier la base de règles, de consulter l'occupation des piles, … .

A l'ouverture de la session, le prompt de la ligne de commande est affiché dans la console et l'utilisateur peut entrer n'importe quel but Prolog. A la fermeture de la session (par l'effacement de quit par exemple) la mise au point continue où elle en était. Il faut noter que les effets de bord de la session sont gardés: affectations, ajout/suppression de règles, ouverture/fermeture de fichier…

Par exemple, l'exécution de repas(e,Poulet,d) provoque un échec. Mettons au

point :

> repas(e,Poulet,d);

CALL: repas( v156, Poulet, v313) DBG:

CALL: hors_d_oeuvre( v156) DBG:

CALL: plat( Poulet) DBG:

CALL: viande( Poulet) DBG:

RECALL(2): plat( Poulet)

DBG:E pourquoi viande(Poulet) a échoué?

on ouvre une session, > list(viande/1); le prompt Prolog apparait, viande(Grillade_de_boeuf) -> ;

viande(Poulet_au_tilleul) -> ;

{} on consulte la base de règles,

> quit; on termine la session,

Elapsed time: 19s Goodbye...

DBG: w on est de retour sous le debugger!

---> plat / 1 , rule number 2, on backtracking DBG: g

>

Voir les commandes du debugger s, w, E. 6.6.2.6. Configuration

Comme nous l'avons déjà vu précédemment, il est possible de définir les comportements du debugger. L'ensemble des paramètres de comportement est appelé la configuration.

Certains paramètres de la configuration peuvent être précisés au lancement de l'outil, ils seront valides durant la session de mise au point (c'est à dire tant qu'on n'a pas désactivé l'outil ou tant qu'on ne l'a pas ré-activé avec d'autres paramètres) tant qu'une commande ne les modifie pas.

Les comportements paramétrables sont :

à l'activation ou sous l'interpréteur de commande du debugger : la progression dans le code,

le moment d'impression des arguments,

sous l'interpréteur de commande du debugger uniquement : la présentation des informations, dont :

un paramètre d'indentation des messages,

un paramètre de précision ou profondeur d'impression,

le choix des informations : sur les erreurs, entre deux points d'arrêt, pendant la trace.

Une configuration standard est prédéfinie. Elle comprend : arrêt sur le prochain but, impression avant unification, aucune indentation, impression des termes par out en profondeur 4, impression d'un message et de la pile de récursion sur les erreurs. Des commandes de l'outil de mise au point permettent de connaître sa configuration et toute autre information relative à la mise au point :

l'état des options,

la liste des points d'arrêt actifs,

la liste des prédicats non développés en trace, la liste des commandes de l'outil.

Voir les commandes du debugger S, b, j, h et la règle prédéfinie show_spy. 6.6.3. Activation d'un mode de mise au point

L'outil de mise au point est activé dès l'effacement de l'un des prédicats prédéfinis trace/0, trace/1, debug/0, debug/1 ou debug/2. Il est désactivé par l'effacement de l'un des prédicats prédéfinis no_trace/0 ou no_debug/0.

Un drapeau permet de définir la configuration de démarrage. Il vaut la somme des valeurs choisies parmi :

1 arrêt sur le premier appel de but. 2 arrêt sur le premier point d'arrêt fixe. 8 Option impression après unification validée 16 Option impression avant unification validée

Un drapeau égal à 0 correspond à la configuration standard prédéfinie. debug(n) ou

debug(n, f)

sont les prédicats généraux d'activation du debugger. n doit être un entier connu au moment de l'appel. Il précise la configuration de démarrage, telle qu'elle a été définie ci-dessus. Quand n vaut 0, cela correspond à la configuration prédéfinie standard. f est un nom de fichier dans lequel seront redirigées les impressions du debugger. Le fichier est fermé à l'exécution d'un prédicat de désactivation de l'outil de mise au point. Si les impressions de mise au point étaient déjà redirigées, le précédent fichier est fermé pour être remplacé par f.

debug ou debug(f)

sont les prédicats de réactivation du debugger. Ils ne modifient pas la configuration en place. f est un nom de fichier dans lequel seront redirigées les impressions du debugger. Le fichier est fermé à l'exécution d'un prédicat de désactivation de l'outil de mise au point. Si les impressions de mise au point étaient déjà redirigées, le précédent fichier est fermé pour être remplacé par f.

trace ou trace(f)

sont des prédicats particuliers du debugger. Ils l'activent dans une configuration précise, celle du mode trace, représentée par le drapeau 19. f est un nom de fichier dans lequel seront redirigées les impressions du debugger. Le fichier est fermé à l'exécution d'un prédicat de désactivation de l'outil de mise au point. Si les impressions de mise au point étaient déjà redirigées, le précédent fichier est fermé pour être remplacé par f.

no_trace ou no_debug

sont des prédicats de désactivation de l'outil de mise au point, quel que soit son mode, sans altérer la configuration en place.

Dans le document Apprendre et enseigner Prolog pdf (Page 148-160)