# Introduction

Ce didacticiel est destiné aux utilisateurs maîtrisant la programmation en C ou C++, ainsi que l'architecture de la Simple Virtual Machine.

Dans ce didacticiel, vous allez apprendre à introspecter et configurer les extensions depuis les extensions.

Le temps de lecture de ce didacticiel est estimé à 25 minutes.

# Mise en place

Pour commencer, créez le canevas de l'extension dans le fichier extensions.svm_plugin en utilisant ce code :

    PLUGIN plugin

    DEFINE

# Introspection

## Affichage

La première manière d'introspecter les extensions est de demander la représentation textuelle des objets des extensions présents sur la machine virtuelle.

Modifiez le code de l'extension :

    PLUGIN plugin

    DEFINE

    INSTRUCTION plugin.print -> STR
    %{
    	SVM_String s = ::svm_plugin_print(svm);
    	return NEW_VALUE(string,s);
    %}

Générez et compilez cette extenion, puis créez une petite application de test pour appeller cette instruction et en afficher le résultat dans le terminal.

## Présence d'objets

Il est également possible de déterminer la présence d'objets de manière plus fine.

### Requêtes simples

Commençons par les objets dont le seul nom suffit pour tester s'ils sont accessibles dans la machine virtuelle.

Modifiez le code de l'extension :

    PLUGIN plugin

    DEFINE

    INSTRUCTION plugin.type PEP -> BLN
    %{
    	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Boolean b = ::svm_plugin_has_type(svm,n);
    	return NEW_VALUE(boolean,b);
    %}

    INSTRUCTION plugin.interruption PEP -> BLN
    %{
    	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Boolean b = ::svm_plugin_has_interruption(svm,n);
    	return NEW_VALUE(boolean,b);
    %}

    INSTRUCTION plugin.sequencer PEP -> BLN
    %{
    	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Boolean b = ::svm_plugin_has_sequencer(svm,n);
    	return NEW_VALUE(boolean,b);
    %}

    INSTRUCTION plugin.scheduler PEP -> BLN
    %{
    	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Boolean b = ::svm_plugin_has_scheduler(svm,n);
    	return NEW_VALUE(boolean,b);
    %}

    INSTRUCTION plugin.structure PEP -> BLN
    %{
    	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Boolean b = ::svm_plugin_has_structure(svm,n);
    	return NEW_VALUE(boolean,b);
    %}

Générez et compilez cette extenion, puis créez une petite application de test pour appeller ces instructions.

Ces fonctions d'interface programmatique sont très utiles pour tester une dépendance vers une autre extension de manière plus souple qu'avec la section `USE` des extensions : en effet, ici, la machine peut quand même démarrer et la dépendance est testée plus tard dans l'exécution de l'application. De même, cela permet de tester des dépendances dynamiquement, comme présenté dans l'exemple ci-dessus.

### Requêtes élaborées

Vous pouvez remarquer qu'il manque deux objets dans les requêtes simples : les instructions et les fonctions. En réalité, savoir qu'une instruction ou une fonction existe n'est pas utile en soit. Il est bien plus intéressant de vérifier si un appel à une instruction ou à une fonction est possible.

Modifiez le code de l'extension :

    PLUGIN plugin

    DEFINE

    INSTRUCTION plugin.instruction PEP VALUE * -> BLN
    %{
    	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Parameter r = ::svm_parameter_value_new(svm,::svm_value_boolean_new(svm,TRUE));
    	SVM_Boolean b = ::svm_plugin_has_instruction(svm,n,argc-1,argv+1,r);
    	return NEW_VALUE(boolean,b);
    %}

    INSTRUCTION plugin.function PEP -> BLN
    %{
    	SVM_Value_PluginEntryPoint n = ::svm_parameter_value_get(svm,argv[0]);
    	SVM_Parameter *p = ::svm_parameter_array_new(svm,3);
    	p[0] = ::svm_parameter_value_new(svm,::svm_value_integer_new_null(svm));
    	p[1] = ::svm_parameter_marker_new__raw(svm,"<");
    	p[2] = ::svm_parameter_value_new(svm,::svm_value_string_new_null(svm));
    	SVM_Parameter r = ::svm_parameter_value_new(svm,::svm_value_boolean_new(svm,TRUE));
    	SVM_Boolean b = ::svm_plugin_has_function(svm,n,3,p,r);
    	return NEW_VALUE(boolean,b);
    %}

Générez et compilez cette extenion, puis créez une petite application de test pour appeller la première instruction avec ce code :

    #!/usr/bin/env svm
    LOG
    PLUGIN "svmcom.so"
    LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so"

    PROCESS "plugin"
    	CODE "main" INLINE
    		:memory BLN*5/b
    		:plugin.instruction plugin.instruction p.e -> (b/0)
    		:plugin.instruction plugin.void 3 -> (b/1)
    		:plugin.instruction plugin.instruction "a" -> (b/2)
    		:plugin.instruction plugin.instruction p.e 1 2 3 -> (b/3)
    		:plugin.instruction com.message 1 2 3 -> (b/4)
    		:com.message @(b/0) " " @(b/1) " " @(b/2) " " @(b/3) " " @(b/4)
    	END
    END

Vous pouvez lancer cette application :

    ./plugin.svm
    TRUE FALSE FALSE TRUE FALSE

Interprétons alors le résultat :

1. Le premier booléen est vrai, car l'instruction `plugin.instruction` accepte un point d'entrée d'extension et retourne un booléen,
2. Le second est faux, car l'instruction `plugin.void` n'existe pas,
3. Le troisième booléen est faux, car l'instruction `plugin.instruction` n'accepte pas une chaine de caractères comme paramètre,
4. Le quatrième booléen est vrai, car l'instruction `plugin.instruction` accepte un point d'entrée d'extension suivi d'un nombre arbitraire de valeurs, et retourne un booléen,
5. Le dernier booléen est faux, car l'instruction `com.message` accepte n'importe quel nombre de valeurs, mais ne retourne pas un booléen.

La fonction `svm_plugin_has_function` réalise le même test sur les fonctions.

- Les fonctions `svm_plugin_has_type`, `svm_plugin_has_interruption`, `svm_plugin_has_sequencer`, `svm_plugin_has_scheduler` et `svm_plugin_has_structure` permettent de détecter la présence d'un objet d'extension de manière plus souple que la directive `USE` des définitions d'extension.
- Les fonctions `svm_plugin_has_instruction` et `svm_plugin_has_function` permettent de détecter si l'appel à une instruction ou à une fonction peut être réalisé sans que l'appel lui-même soit erroné. Ces deux tests comprennent la vérification des types de paramètres et de la valeur de retour des instructions et des fonctions.

# Configuration

En plus des objets destinés à l'application et à la machine virtuelle, les extensions permettent de définir des objets servant à la configuration des extensions depuis l'application. Ces objets sont accessibles depuis toutes les fonctions de l'extension, y compris ses fonctions d'initialisation, de démarrage et de finalisation.

## Options

Modifiez le code de l'extension :

    PLUGIN plugin

    DEFINE

    OPTION plugin.flag -f BLN

    OPTION plugin.integer -i INT

    OPTION plugin.string -s STR

    INSTRUCTION plugin.option PEP -> VALUE ?
    %{
    	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
    	if(not ::svm_plugin_has_option(svm,o))
    	{
    		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown option.",SOFTWARE);
    	}
    	return ::svm_plugin_get_option(svm,o);
    %}

Avant de générer l'extension, constatez que :

- Un nouveau type d'objet extension est défini : ce type permet de déclarer une option d'extension.
- A chaque option associée à un point d'entrée d'extension est rattaché une clef commançant par un tiret et un type de retour parmi :
  - BLN : cette option sera un drapeau sans argument dans la ligne de commande de l'extension. La valeur sera `FALSE` si l'option n'est pas présente, et `TRUE` si elle l'est,
  - INT : cette option demandera un entier comme argument. Si l'option n'est pas présente sur la ligne de commande, elle vaudra un entier nul,
  - STR : cette option demandera une chaine de caractères entre double quotes comme argument. Si l'option n'est pas présente sur la ligne de commande, elle vaudra une chaine nulle.
- La fonction `svm_plugin_has_option` permet de détecter la présence d'un objet option, comme les autres objets d'extension. Notez que seule la définition de l'option est vérifiée, et pas sa présence sur la ligne de commande de l'extension.
- La fonction `svm_plugin_get_option` permet de récupérer la valeur associée à une option.

Générez et compilez l'extension. Puis, créez une petite application avec ce code :

    #!/usr/bin/env svm
    LOG
    PLUGIN "svmrun.so"
    LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so"

    PROCESS "plugin"
    	CODE "main" INLINE
    		:memory AUTO*4/o
    		:plugin.option plugin.flag -> (o/0)
    		:plugin.option plugin.integer -> (o/1)
    		:plugin.option plugin.string -> (o/2)
    		:plugin.option plugin.unknown -> (o/3)
    		:run.coredump STDOUT
    	END
    END

Lancez cette application :

    ./plugin.svm
    Kernel:
    State: R, transmit_interruption

    Processor:
     State:
       Next instruction: <main:1/6> (Current one: <main:1/5>)
       Current memory  : &0*0
       Allocated memory: &0*4
       Aliases         : o
       Flags           :
       Cascaded flags  :
       Local interruption handlers:
       Cascaded local interruption handlers:
     Saved states:
     Global interruption handlers:
     Waiting interruptions:

    Code at <main:1/5>:
         :memory AUTO*4/o   # <0> @(main, line 1)
         :plugin.option plugin.flag -> (o/0)        # <1> @(main, line 2)
         :plugin.option plugin.integer -> (o/1)     # <2> @(main, line 3)
         :plugin.option plugin.string -> (o/2)      # <3> @(main, line 4)
         :plugin.option plugin.unknown -> (o/3)     # <4> @(main, line 5)
    ====== HERE ======
         :run.coredump STDOUT       # <5> @(main, line 6) SYSTEM
    ==================
     Labels:
     Symbols:

    Memory:
      &0: BLN FALSE
      &1: INT
      &2: STR
      &3: IRQ FAILURE
     Aliases:
      o -> &0*4
     Free space:

Modifiez le code de l'application pour ajouter les options d'extension :

    #!/usr/bin/env svm
    LOG
    PLUGIN "svmrun.so"
    LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so" -f -i 17 -s "value"

    PROCESS "plugin"
    	CODE "main" INLINE
    		:memory AUTO*4/o
    		:plugin.option plugin.flag -> (o/0)
    		:plugin.option plugin.integer -> (o/1)
    		:plugin.option plugin.string -> (o/2)
    		:plugin.option plugin.unknown -> (o/3)
    		:run.coredump STDOUT
    	END
    END

Puis relancez l'application :

    ./plugin.svm
    Kernel:
    State: R, transmit_interruption

    Processor:
     State:
       Next instruction: <main:1/6> (Current one: <main:1/5>)
       Current memory  : &0*0
       Allocated memory: &0*4
       Aliases         : o
       Flags           :
       Cascaded flags  :
       Local interruption handlers:
       Cascaded local interruption handlers:
     Saved states:
     Global interruption handlers:
     Waiting interruptions:

    Code at <main:1/5>:
         :memory AUTO*4/o   # <0> @(main, line 1)
         :plugin.option plugin.flag -> (o/0)        # <1> @(main, line 2)
         :plugin.option plugin.integer -> (o/1)     # <2> @(main, line 3)
         :plugin.option plugin.string -> (o/2)      # <3> @(main, line 4)
         :plugin.option plugin.unknown -> (o/3)     # <4> @(main, line 5)
    ====== HERE ======
         :run.coredump STDOUT       # <5> @(main, line 6) SYSTEM
    ==================
     Labels:
     Symbols:

    Memory:
      &0: BLN TRUE
      &1: INT 17
      &2: STR "value"
      &3: IRQ FAILURE
     Aliases:
      o -> &0*4
     Free space:

Vous pouvez noter également que les valeurs sont visibles depuis le débugueur :

plugin
Plugins

Definitions
INSTRUCTION plugin.option PEP -> VALUE ?
SYSTEM INSTRUCTION run.coredump [ 'STDOUT' 'STDERR' 'TRACE' ] ? -> STR
INSTRUCTION run.interrupt IRQ
INSTRUCTION run.interruption [ 'GLOBAL' 'CASCADE' ] ? [ STR SYM ] : target ?
INSTRUCTION run.loop MUTABLE INT : index ( { INT : step } ) ? [ < > ] INT : limit STR : label
WAITING INSTRUCTION run.parallel_call SYM : function PTR : parameters STR : name ( 'PM' = BLN : protected_mode | 'SEQ' = [ 'DEFAULT' PEP ] : sequencer | 'SCHED' = [ 'DEFAULT' PEP ] : scheduler | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory | 'PARAMS' = [ 'COPY' 'MOVE' 'SHARE' ] ) *
INSTRUCTION run.parallel_limit [ 'NONE' INT ]
INSTRUCTION run.protected_call SYM : function PTR : parameters ( 'NOIRQ' | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory ) *
INSTRUCTION run.retry INT ? : number_instructions
WAITING INSTRUCTION run.sleep [ 'HARD' 'SOFT' ] INT : seconds
INSTRUCTION run.suspend
SYSTEM INSTRUCTION run.trace 'LOCATION' ? VALUE +
SCHEDULER run.parallel
SCHEDULER run.rrpreempt
SEQUENCER run.stack
OPTION plugin.flag -f BLN (TRUE)
OPTION plugin.integer -i INT (17)
OPTION plugin.string -s STR ("value")

in
Filter

## Arguments

En plus des options, les extensions peuvent définir des arguments obligatoires, ainsi que des arguments optionnels, présents en extra sur la ligne de commande de l'extension.

Modifiez le code de l'extension :

    PLUGIN plugin

    DEFINE

    OPTION plugin.flag -f BLN

    OPTION plugin.integer -i INT

    OPTION plugin.string -s STR

    INSTRUCTION plugin.option PEP -> VALUE ?
    %{
    	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
    	if(not ::svm_plugin_has_option(svm,o))
    	{
    		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown option.",SOFTWARE);
    	}
    	return ::svm_plugin_get_option(svm,o);
    %}

    ARGUMENT plugin.integer INT

    ARGUMENT plugin.string STR

    ARGUMENTS plugin.extra

    INSTRUCTION plugin.argument PEP -> VALUE
    %{
    	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
    	if(not ::svm_plugin_has_argument(svm,o))
    	{
    		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown argument.",SOFTWARE);
    	}
    	return ::svm_plugin_get_argument(svm,o);
    %}

    INSTRUCTION plugin.arguments PEP -> [ PTR IRQ ]
    %{
    	SVM_Value_PluginEntryPoint o = ::svm_parameter_value_get(svm,argv[0]);
    	if(not ::svm_plugin_has_arguments(svm,o))
    	{
    		return ::svm_value_interruption_new_internal__raw(svm,FAILURE,"Unknown extra arguments.",SOFTWARE);
    	}
    	SVM_Value *t = ::svm_plugin_get_arguments(svm,o);
    	SVM_Size s=0;
    	for(SVM_Value *it=t ; *it ; ++it) { ++s; }
    	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
    	::svm_memory_zone_append_internal__raw(svm,z,AUTOMATIC,s);
    	SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z);
    	::svm_memory_write_pointer(svm,CURRENT(kernel),p,t);
    	return p;
    %}

De la même manière que pour les options, l'extension définit ici deux arguments obligatoire avec leur type associé, et les arguments en extra qui eux n'ont pas de type associé.

Générez et compilez l'extension. Puis, modifiez le code de l'application :

    #!/usr/bin/env svm
    LOG
    PLUGIN "svmrun.so"
    LOCAL PLUGIN "svmpluginplugin/libsvmplugin.so" -f -i 17 -s "value" -- 3 "other" 1 2 3 "four" 5

    PROCESS "plugin"
    	CODE "main" INLINE
    		:memory AUTO*4/o, AUTO*5/a
    		:plugin.option plugin.flag -> (o/0)
    		:plugin.option plugin.integer -> (o/1)
    		:plugin.option plugin.string -> (o/2)
    		:plugin.option plugin.unknown -> (o/3)
    		:plugin.argument plugin.integer -> (a/0)
    		:plugin.argument plugin.string -> (a/1)
    		:plugin.argument plugin.unknown -> (a/2)
    		:plugin.arguments plugin.extra -> (a/3)
    		:plugin.arguments plugin.unknown -> (a/4)
    		:run.coredump STDOUT
    	END
    END

Cette fois, l'exécution donne :

    ./plugin.svm
    Kernel:
    State: R, transmit_interruption

    Processor:
     State:
       Next instruction: <main:1/11> (Current one: <main:1/10>)
       Current memory  : &0*0
       Allocated memory: &0*14
       Aliases         : a o
       Flags           :
       Cascaded flags  :
       Local interruption handlers:
       Cascaded local interruption handlers:
     Saved states:
     Global interruption handlers:
     Waiting interruptions:

    Code at <main:1/10>:
         :memory AUTO*4/o , AUTO*5/a        # <0> @(main, line 1)
         :plugin.option plugin.flag -> (o/0)        # <1> @(main, line 2)
         :plugin.option plugin.integer -> (o/1)     # <2> @(main, line 3)
         :plugin.option plugin.string -> (o/2)      # <3> @(main, line 4)
         :plugin.option plugin.unknown -> (o/3)     # <4> @(main, line 5)
         :plugin.argument plugin.integer -> (a/0)   # <5> @(main, line 6)
         :plugin.argument plugin.string -> (a/1)    # <6> @(main, line 7)
         :plugin.argument plugin.unknown -> (a/2)   # <7> @(main, line 8)
         :plugin.arguments plugin.extra -> (a/3)    # <8> @(main, line 9)
         :plugin.arguments plugin.unknown -> (a/4)  # <9> @(main, line 10)
    ====== HERE ======
         :run.coredump STDOUT       # <10> @(main, line 11) SYSTEM
    ==================
     Labels:
     Symbols:

    Memory:
      &0: BLN TRUE
      &1: INT 17
      &2: STR "value"
      &3: IRQ FAILURE
      &4: INT 3
      &5: STR "other"
      &6: IRQ FAILURE
      &7: PTR &9*5
      &8: IRQ FAILURE
      &9: INT 1
      &10: INT 2
      &11: INT 3
      &12: STR "four"
      &13: INT 5
     Aliases:
      a -> &4*5
      o -> &0*4
     Free space:

Notez que si moins de deux arguments sont présents sur la ligne de commande de l'extension, la machine refuse de démarrer.

Ici, vous pouvez retrouver la valeur des deux premiers arguments aux adresses `&4` et `&4`. Les valeurs des arguments en extra sont quant à eux écrits en mémoire et accessible via le pointeur `&9*5`.

Tout comme les options, les valeurs d'arguments sont visibles dans le débugueur avec la position de l'argument sur la ligne de commande :

plugin
Plugins

Definitions
INSTRUCTION plugin.argument PEP -> VALUE
INSTRUCTION plugin.arguments PEP -> [ PTR IRQ ]
INSTRUCTION plugin.option PEP -> VALUE ?
SYSTEM INSTRUCTION run.coredump [ 'STDOUT' 'STDERR' 'TRACE' ] ? -> STR
INSTRUCTION run.interrupt IRQ
INSTRUCTION run.interruption [ 'GLOBAL' 'CASCADE' ] ? [ STR SYM ] : target ?
INSTRUCTION run.loop MUTABLE INT : index ( { INT : step } ) ? [ < > ] INT : limit STR : label
WAITING INSTRUCTION run.parallel_call SYM : function PTR : parameters STR : name ( 'PM' = BLN : protected_mode | 'SEQ' = [ 'DEFAULT' PEP ] : sequencer | 'SCHED' = [ 'DEFAULT' PEP ] : scheduler | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory | 'PARAMS' = [ 'COPY' 'MOVE' 'SHARE' ] ) *
INSTRUCTION run.parallel_limit [ 'NONE' INT ]
INSTRUCTION run.protected_call SYM : function PTR : parameters ( 'NOIRQ' | 'CPU' = INT : max_instructions | 'RAM' = INT : max_memory ) *
INSTRUCTION run.retry INT ? : number_instructions
WAITING INSTRUCTION run.sleep [ 'HARD' 'SOFT' ] INT : seconds
INSTRUCTION run.suspend
SYSTEM INSTRUCTION run.trace 'LOCATION' ? VALUE +
SCHEDULER run.parallel
SCHEDULER run.rrpreempt
SEQUENCER run.stack
OPTION plugin.flag -f BLN (TRUE)
OPTION plugin.integer -i INT (17)
OPTION plugin.string -s STR ("value")
ARGUMENT plugin.integer INT (@0 3)
ARGUMENT plugin.string STR (@1 "other")
ARGUMENTS plugin.extra ([ 1 2 3 "four" 5 ])

in
Filter

- Les options et les arguments d'extension laisse la possibilité aux extensions d'être configurables depuis le fichier d'application.
- Les fonctions `svm_plugin_has_option`, `svm_plugin_has_argument` et `svm_plugin_has_arguments` permettent de détecter l'existence des définitions d'options et d'arguments. Cette détection permet une gestion des dépendances plus souple que celle, toujours possible avec les options et arguments, de la directive `USE`.
- Les fonctions `svm_plugin_get_option`, `svm_plugin_get_argument` et `svm_plugin_get_arguments` permettent de récupérer les valeurs associées aux options et arguments. Ces valeurs proviennent directement du fichier d'application, depuis la ligne d'invocation de l'extension.
- Les valeurs d'options et d'arguments sont disponibles dans toutes les fonctions de l'extension, y compris celles d'initialisation, de démarrage et de finalisation.
- Les options et arguments sont des objets d'extension comme les autres : ils peuvent être utilisés depuis n'importe quel autre extension. Cela permet en particulier d'aligner le comportement de plusieurs extensions sur des options ou arguments nécessaires à d'autres extensions.

# Conclusion

Vous venez de voir comment introspecter et configurer les extensions depuis une extension.

L'introspection d'extension permet de manière générale d'assouplir la gestion des dépendances entre extensions, ainsi que de réaliser des dépendances dynamiques.

L'introspection d'extension est souvent utile lorsqu'une extension réalise un polymorphisme dynamique inter-extension, car il permet de réagir à l'absence d'une fonction de manière moins brutale que ce qui a pu être décrit dans un didacticiel précédent.

La possibilité de rendre les extensions configurables au travers d'options et d'arguments permet aux applications de les utiliser avec une plus grande souplesse. En particulier, une option pour empêcher une extension de créer des processus au démarrage peut être souhaitable, ainsi que de pouvoir changer certaines valeurs utilisées par les extensions dans leur fonctionnement pour éviter les collisions de comportement entre extensions.