# Introduction

Ce didacticiel est destiné aux nouveaux utilisateurs de la Simple Virtual Machine.

Dans ce didacticiel, vous allez rattrapper les erreurs levées par les instructions.

Le temps de lecture de ce didacticiel est estimé à 15 minutes si les fonctions ont été abordées.

# Mise en place

Pour commencer, créez le canevas de l'application dans le fichier exécutable interruptions.svm en utilisant ce code :

    #!/usr/bin/env svm
    DESCRIPTION
    Software interruptions example
    END
    LOG
    DEBUG "Software interruptions" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmstr.so"
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/integer, STR/command
    	:label wait
    		:com.prompt "command" STR -> &command
    		:str.join "command_" @&command -> &command
    		:call @&command integer
    		:goto wait
    	:label command_quit
    		:shutdown
    	:label command_set
    		:com.prompt "integer" INT -> &integer
    		:return
    	:label command_inc
    		:shift &integer
    		:return
    	:label command_dec
    		:shift -1 &integer
    		:return
    	:label command_print
    		:com.message "integer: " @&integer
    		:return
    	END
    END

# Les interruptions

## Erreurs d'instructions

Lancez l'application sans aucune option. Un prompt apparaît, et vous pouvez lancer des commandes :

    ./interruptions.svm
    command: set
    integer: 17
    command: inc
    command: inc
    command: print
    integer: 19
    command: exit
    ### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
    Kernel interrupted: @(main, line 5) Interruption FAILURE not handled: Label command_exit is not defined in code main.

    Core dump:
    Kernel:
    State: I, transmit_interruption, interrupted @(main, line 5) Interruption FAILURE not handled: Label command_exit is not defined in code main.

    Processor:
     State:
       Next instruction: <main:1/4> (Current one: <main:1/3>)
       Current memory  : &0*0
       Allocated memory: &0*2
       Aliases         : command integer
       Flags           :
       Cascaded flags  :
       Local interruption handlers:
       Cascaded local interruption handlers:
     Saved states:
     Global interruption handlers:

    Code at <main:1/3>:
         :memory INT/integer , STR/command  # <0> @(main, line 1)
      :label wait
         :com.prompt "command" STR -> &command      # <1> @(main, line 3) WAITING
         :str.join "command_" @&command -> &command # <2> @(main, line 4)
    ====== HERE ======
         :call @&command integer    # <3> @(main, line 5)
    ==================
         :goto wait # <4> @(main, line 6)
      :label command_quit
         :shutdown  # <5> @(main, line 8)
      :label command_set
         :com.prompt "integer" INT -> &integer      # <6> @(main, line 10) WAITING
         :return    # <7> @(main, line 11)
      :label command_inc
         :shift &integer    # <8> @(main, line 13)
         :return    # <9> @(main, line 14)
      :label command_dec
         :shift -1 &integer # <10> @(main, line 16)
         :return    # <11> @(main, line 17)
      :label command_print
         :com.message "integer: " @&integer # <12> @(main, line 19)
         :return    # <13> @(main, line 20)
     Labels:
      command_dec -> <10>
      command_inc -> <8>
      command_print -> <12>
      command_quit -> <5>
      command_set -> <6>
      wait -> <1>
     Symbols:

    Memory:
      &0: INT 19
      &1: STR "command_exit"
     Aliases:
      command -> &1*1
      integer -> &0*1
     Free space:

    ### Simple Virtual Machine 1234 : SYSTEM | 2023-01-01 00:00:00 GMT ###################################################################################
    Process application interrupted: @(main, line 5) Interruption FAILURE not handled: Label command_exit is not defined in code main.

Et là, c'est un échec. A la moindre erreur de l'utilisateur de l'application, celle-ci s'arrête en crachant sur le terminal une erreur certes très utile pour le développeur, mais peu agréable pour l'utilisateur.

## Interruption du processeur

Cependant, avant d'agir sur cette condition d'erreur, lisez attentivement le texte généré. Il y apparaît une information importante : il s'agit d'une interruption de type `FAILURE`.

Comme vous l'avez déjà constaté, l'application est exécutée par un processeur. En cas d'erreur, comme ici, une interruption est émise, et le processeur arrête d'exécuter l'application : on parle d'interruption non rattrapée.

Une condition d'erreur dans une instruction est matérialisée par une interruption du processeur.

Lorsque le processeur est interrompu, il arrête l'exécution du code.

## Quelques types d'interruption

Les types d'interruption fréquemment rencontrés sont :

- `FAILURE` pour les erreurs d'ordre général ou dûes à de mauvaises valeurs d'entrée,
- `MEMORY` pour les erreurs liées à la mémoire,
- `PROCESSOR` pour les erreurs qui pousserait le processeur à terminer dans un état invalide,
- `NUMERIC` pour les erreurs numériques et de calcul,
- `DEVICE` pour les erreurs d'entrée/sortie,
- `TERMINATE` pour indiquer une demande de terminaison de l'application.

# Gestionnaire d'interruption

## Rattrapons les interruptions

Ce que l'on aimerait faire dans cette application est de pouvoir exécuter un code spécial en cas d'erreur, pour :

- éviter un arrêt brutal de l'application,
- rétablir la situation en adoptant un comportement par défaut,
- ou parfois ignorer simplement l'erreur,
- ou même altérer le flot d'instructions.

Modifiez le code de l'application pour obtenir :

    #!/usr/bin/env svm
    DESCRIPTION
    Software interruptions example
    END
    LOG
    DEBUG "Software interruptions" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmstr.so"
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/integer, STR/command
    	:label wait
    		:com.prompt "command" STR -> &command
    		:str.join "command_" @&command -> &command
    		:interruption FAILURE wrong_command
    		:debug BREAK
    		:call @&command integer
    		:interruption FAILURE
    		:goto wait
    	:label command_quit
    		:shutdown
    	:label command_set
    		:com.prompt "integer" INT -> &integer
    		:return
    	:label command_inc
    		:shift &integer
    		:return
    	:label command_dec
    		:shift -1 &integer
    		:return
    	:label command_print
    		:com.message "integer: " @&integer
    		:return
    	:label wrong_command
    		:com.message "Invalid command."
    		:return
    	END
    END

Lancez maintenant l'application :

    ./interruptions.svm
    command: set
    integer: 17
    command: inc
    command: inc
    command: dec
    command: exit
    Invalid command.
    command: quit

L'erreur est maintenant issue d'une instruction de l'application, et l'application continue de s'exécuter.

Relancez l'application en mode débugueur, et regardez le processeur lorsqu'il exécute l'instruction `:call` :

Software interruptions
Processor - K main - P application
State:
Next instruction: <main:1/5>
Current instruction: <main:1/4>
Code
Current memory: &0*0
Allocated memory: &0*2
Defined aliases: command integer
Local interruptions: FAILURE => <main:1/17>
Code
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
Global interruptions:
Waiting interruptions:
=Code main - K main - P application

    :memory INT/integer , STR/command
    :label wait
    :com.prompt "command" STR -> &command
    :str.join "command_" @&command -> &command
    :interruption FAILURE wrong_command
    :debug BREAK
    :call @&command integer
    :interruption FAILURE
    :goto wait
    :label command_quit
    :shutdown
    :label command_set
    :com.prompt "integer" INT -> &integer
    :return
    :label command_inc
    :shift &integer
    :return
    :label command_dec
    :shift -1 &integer
    :return
    :label command_print
    :com.message "integer: " @&integer
    :return
    :label wrong_command
    :com.message "Invalid command."
    :return

Auto-scroll to
Current
with
above
Display

Dans la fenêtre du processeur, un lien existe entre l'interruption `FAILURE` et l'adresse dans le code indiquée par l'étiquette "wrong_command" : il s'agit d'un gestionnaire d'interruption.

Quand ce gestionnaire est défini, au lieu d'arrêter toute l'exécution lorsqu'une interruption `FAILURE` arrive, le processeur va réaliser un appel de fonction à l'adresse indiquée.

Exécutez pas à pas l'application, et observez ce qu'il se passe lorsqu'une commande inconnue est entrée dans le terminal : l'exécution normale du code est bien interrompue, et un appel implicite à "wrong_command" est effectué.

## Types de gestionnaires d'interruption

La gestion des interruptions est plutôt sensible dans une application. Pour couvrir un maximum de besoins, il existe trois niveaux de gestionnaires d'interruptions :

1. Gestionnaire local : un gestionnaire d'interruption local est actif uniquement dans la fonction où est défini le gestionnaire d'interruption,
2. Gestionnaire cascadé : un gestionnaire d'interruption cascadé est actif dans la fonction où est défini le gestionnaire d'interruption et toutes les fonctions appellées depuis cette fonction,
3. Gestionnaire global : un gestionnaire d'interruption global est actif tout le temps.

Lorsque plusieurs gestionnaires sont définis pour une interruption donnée, le premier dans la liste ci-dessus est utilisé.

Pour illustrer cet ordre de précédence, remplacez le code par :

    #!/usr/bin/env svm
    DESCRIPTION
    Software interruptions example
    END
    LOG
    DEBUG "Software interruptions" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmstr.so"
    PLUGIN "svmint.so"
    PROCESS "application"
    	CODE "main" INLINE
    		:memory INT/integer, STR/command
    		:interruption GLOBAL NUMERIC wrong_operation
    	:label wait
    		:com.prompt "command" STR -> &command
    		:str.join "command_" @&command -> &command
    		:interruption FAILURE wrong_command
    		:debug BREAK
    		:call @&command integer
    		:interruption FAILURE
    		:goto wait
    	:label command_quit
    		:shutdown
    	:label command_set
    		:interruption NUMERIC use_zero
    		:com.prompt "integer" INT -> &integer
    		:return
    	:label use_zero
    		:com.message "Use zero."
    		0 -> &integer
    		:return 2
    	:label command_inc
    		:shift &integer
    		:return
    	:label command_dec
    		:shift -1 &integer
    		:return
    	:label command_div
    		:int.div 100 @&integer -> &integer
    		:return
    	:label command_print
    		:com.message "integer: " @&integer
    		:return
    	:label wrong_command
    		:com.message "Invalid command."
    		:return
    	:label wrong_operation
    		:com.message "Invalid operation."
    		:return
    	END
    END

Puis lancez cette application (potentiellement pas à pas dans le débugueur) :

    ./interruptions.svm
    command: set
    integer: 17
    command: set
    integer: one
    Use zero.
    command: div
    Invalid operation.
    command: quit

Dans cet exemple, vous pouvez constater plusieurs choses :

- lorsqu'un entier invalide a été entré, la fonction appellée est "use_zero" avec une interruption `NUMERIC` (gestionnaire local plus prioritaire que le gestionnaire global),
- lorsqu'une division par zéro a été effectuée, la fonction appellée est "wrong_operation" également avec une interruption `NUMERIC` (gestionnaire global),
- il est courant d'utiliser `:return 2` dans un gestionnaire d'interruption pour couper l'exécution de la fonction où s'est produite l'interruption.

Un gestionnaire d'interruption est une fonction qui sera appellée lorsqu'une interruption est levée.

- Un gestionnaire d'interruption est défini avec l'instruction `:interruption interruption cible`.
- Un gestionnaire d'interruption est retiré avec l'instruction `:interruption interruption`.
- Pour réaliser ces opérations sur un gestionnaire cascadé, il faut utiliser le mot-clef `CASCADE` après l'instruction `:interruption`.
- Pour réaliser ces opérations sur un gestionnaire global, il faut utiliser le mot-clef `GLOBAL` après l'instruction `:interruption`.

Pour chaque interruption, il peut y avoir jusqu'à trois gestionnaires d'interruption dans cet ordre de priorité :

1. local,
2. cascadé,
3. global.

# Conclusion

Vous venez de voir comment gérer les erreurs émises par les instructions.

Une gestion des erreurs complète donne de la robustesse à une application. Il convient de bien anticiper où peuvent apparaître des erreurs et y apporter le traitement approprié.