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 créer et manipuler des fonctions d'extension de la machine virtuelle.

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

Mise en place

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

PLUGIN func

DEFINE

Définition

Tout comme les structures sont des sortes de types réservés aux extensions, les fonctions sont des sortes d'instructions réservées aux extensions. Il est donc possible d'appeler ces nouveaux objets uniquement depuis les extensions, mais pas depuis le code de la machine virtuelle.

Paramètres

Une fonction se définit un peu de la même manière que les instructions, avec quelques différences :

Modifiez le code de l'extension :

PLUGIN func

DEFINE

STRUCT func.a
%{
	std::string _s;
%}
delete default: %{}
copy default: %{}

FUNCTION func.a $func.a MUTABLE STRUCT
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Structure b = ::svm_parameter_structure_get(svm,argv[1]);
	SVM_Value_PluginEntryPoint t = ::svm_structure_get_type(svm,b);
	if(::svm_value_is_equal(svm,t,CONST_PEP(func,a)))
	{
		auto rb = reinterpret_cast<struct_a*>(::svm_structure_get_internal(svm,t,b));
		rb->_s = a->_s;
	}
%}

FUNCTION func.b MUTABLE $func.a VARIABLE
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Kernel k = ARGV_VARIABLE(1);
	auto c = ::svm_kernel_get_coredump(svm,k);
	a->_s = RAW_STRING(c);
%}

Valeur de retour

Une fonction peut avoir une valeur de retour, tout comme les instructions. Et encore une fois, les fonctions supportent en valeur de retour les structures et les variables.

Modifiez le code de l'extension :

PLUGIN func

DEFINE

STRUCT func.a
%{
	std::string _s;
%}
delete default: %{}
copy default: %{}

FUNCTION func.a $func.a MUTABLE STRUCT
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Structure b = ::svm_parameter_structure_get(svm,argv[1]);
	SVM_Value_PluginEntryPoint t = ::svm_structure_get_type(svm,b);
	if(::svm_value_is_equal(svm,t,CONST_PEP(func,a)))
	{
		auto rb = reinterpret_cast<struct_a*>(::svm_structure_get_internal(svm,t,b));
		rb->_s = a->_s;
	}
%}

FUNCTION func.b MUTABLE $func.a VARIABLE
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Kernel k = ARGV_VARIABLE(1);
	auto c = ::svm_kernel_get_coredump(svm,k);
	a->_s = RAW_STRING(c);
%}

FUNCTION func.c STR -> $func.a
%{
	auto s = ARGV_VALUE(0,string);
	return NEW_STRUCT(func,a,new struct_a { RAW_STRING(s) });
%}

FUNCTION func.d INT -> VARIABLE
%{
	SVM_Value_Integer i = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal(svm,z,AUTOMATIC,i);
	return z;
%}

Vous pouvez générer et compiler l'extension. Mais comme les fonctions ne sont pas appelables depuis le code, il est encore pas possible de les utiliser.

Appel de fonction

Le seul objectif d'une fonction est d'être appelée. Et comme elle ne peut l'être que depuis les extensions, cela signifie que cet appel doit passer par une fonction de l'interface programmatique.

Création des paramètres

La première étape d'un appel de fonction est la création de la liste des paramètres à passer à la fonction.

Modifiez le code de l'extension :

PLUGIN func

includes:
%{
#include <iostream>
%}

DEFINE

STRUCT func.a
%{
	std::string _s;
%}
delete default: %{}
copy default: %{}

FUNCTION func.a $func.a MUTABLE STRUCT
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Structure b = ::svm_parameter_structure_get(svm,argv[1]);
	SVM_Value_PluginEntryPoint t = ::svm_structure_get_type(svm,b);
	if(::svm_value_is_equal(svm,t,CONST_PEP(func,a)))
	{
		auto rb = reinterpret_cast<struct_a*>(::svm_structure_get_internal(svm,t,b));
		rb->_s = a->_s;
	}
%}

FUNCTION func.b MUTABLE $func.a VARIABLE
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Kernel k = ARGV_VARIABLE(1);
	auto c = ::svm_kernel_get_coredump(svm,k);
	a->_s = RAW_STRING(c);
%}

FUNCTION func.c STR -> $func.a
%{
	auto s = ARGV_VALUE(0,string);
	return NEW_STRUCT(func,a,new struct_a { RAW_STRING(s) });
%}

FUNCTION func.d INT -> VARIABLE
%{
	SVM_Value_Integer i = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal(svm,z,AUTOMATIC,i);
	return z;
%}

INSTRUCTION func.example
%{
	auto s = NEW_STRUCT(func,a,new struct_a { "default value" });
	SVM_Parameter *parameters = ::svm_parameter_array_new(svm,2);
	parameters[0] = ::svm_parameter_structure_new(svm,s);
	parameters[1] = ::svm_parameter_variable_new(svm,CURRENT(kernel));
%}

Les paramètres sont préparés dans un tableau créé via la fonction svm_parameter_array_new, et chaque valeur du tableau doit être initialisé avec le résultat d'une fonction svm_parameter_type_new.

Appel

Les paramètres étant prêts, passons à l'appel proprement dit : il se réalise avec la fonction svm_function_call.

Modifiez le code de l'extension :

PLUGIN func

includes:
%{
#include <iostream>
%}

DEFINE

STRUCT func.a
%{
	std::string _s;
%}
delete default: %{}
copy default: %{}

FUNCTION func.a $func.a MUTABLE STRUCT
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Structure b = ::svm_parameter_structure_get(svm,argv[1]);
	SVM_Value_PluginEntryPoint t = ::svm_structure_get_type(svm,b);
	if(::svm_value_is_equal(svm,t,CONST_PEP(func,a)))
	{
		auto rb = reinterpret_cast<struct_a*>(::svm_structure_get_internal(svm,t,b));
		rb->_s = a->_s;
	}
%}

FUNCTION func.b MUTABLE $func.a VARIABLE
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Kernel k = ARGV_VARIABLE(1);
	auto c = ::svm_kernel_get_coredump(svm,k);
	a->_s = RAW_STRING(c);
%}

FUNCTION func.c STR -> $func.a
%{
	auto s = ARGV_VALUE(0,string);
	return NEW_STRUCT(func,a,new struct_a { RAW_STRING(s) });
%}

FUNCTION func.d INT -> VARIABLE
%{
	SVM_Value_Integer i = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal(svm,z,AUTOMATIC,i);
	return z;
%}

INSTRUCTION func.example
%{
	auto s = NEW_STRUCT(func,a,new struct_a { "default value" });
	SVM_Parameter *parameters = ::svm_parameter_array_new(svm,2);
	parameters[0] = ::svm_parameter_structure_new(svm,s);
	parameters[1] = ::svm_parameter_variable_new(svm,CURRENT(kernel));
	::svm_function_call(svm,CONST_PEP(func,b),2,parameters);
	auto rs = reinterpret_cast<struct_a*>(::svm_structure_get_internal(svm,CONST_PEP(func,a),s));
	std::cout << "###" << std::endl << rs->_s << "###" << std::endl;
%}

Notez que l'appel à la fonction se fait en la nommant avec son point d'entrée d'extension : il est alors possible d'appeler une fonction définie par une autre extension ! C'est la machine virtuelle qui fera alors la résolution vers la fonction C/C++ implémentant cette fonction.

Pour les paramètres, deux informations sont données : le nombre de paramètres, et le tableau de paramètres.

Vous pouvez générer et compiler l'extension. Cette fois, créez une application appelant uniquement l'instruction :func.example pour voir l'appel de fonction se réaliser.

Gestion de la valeur de retour

Lorsque la fonction retourne une valeur, cette valeur est simplement retournée par la fonction svm_function_call.

Modifiez le code de l'extension :

PLUGIN func

includes:
%{
#include <iostream>
%}

DEFINE

STRUCT func.a
%{
	std::string _s;
%}
delete default: %{}
copy default: %{}

FUNCTION func.a $func.a MUTABLE STRUCT
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Structure b = ::svm_parameter_structure_get(svm,argv[1]);
	SVM_Value_PluginEntryPoint t = ::svm_structure_get_type(svm,b);
	if(::svm_value_is_equal(svm,t,CONST_PEP(func,a)))
	{
		auto rb = reinterpret_cast<struct_a*>(::svm_structure_get_internal(svm,t,b));
		rb->_s = a->_s;
	}
%}

FUNCTION func.b MUTABLE $func.a VARIABLE
%{
	struct_a* a = ARGV_STRUCT(0,func,a);
	SVM_Kernel k = ARGV_VARIABLE(1);
	auto c = ::svm_kernel_get_coredump(svm,k);
	a->_s = RAW_STRING(c);
%}

FUNCTION func.c STR -> $func.a
%{
	auto s = ARGV_VALUE(0,string);
	return NEW_STRUCT(func,a,new struct_a { RAW_STRING(s) });
%}

FUNCTION func.d INT -> VARIABLE
%{
	SVM_Value_Integer i = ::svm_parameter_value_get(svm,argv[0]);
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal(svm,z,AUTOMATIC,i);
	return z;
%}

INSTRUCTION func.example
%{
	SVM_Value_String d = ::svm_value_string_new__raw(svm,"default value");
	SVM_Parameter *parameters = ::svm_parameter_array_new(svm,2);
	parameters[0] = ::svm_parameter_value_new(svm,d);
	SVM_Structure s = ::svm_function_call(svm,CONST_PEP(func,c),1,parameters);
	parameters[0] = ::svm_parameter_structure_new(svm,s);
	parameters[1] = ::svm_parameter_variable_new(svm,CURRENT(kernel));
	::svm_function_call(svm,CONST_PEP(func,b),2,parameters);
	auto rs = reinterpret_cast<struct_a*>(::svm_structure_get_internal(svm,CONST_PEP(func,a),s));
	std::cout << "###" << std::endl << rs->_s << "###" << std::endl;
%}

Cette fois, la structure modifiée est créée via un appel de fonction (ce qui permet de la créer depuis une autre extension !). Le tableau de paramètres peut, comme il est montré dans cet exemple, être réutilisé tant que le nombre de paramètres effectifs de la fonction ne dépasse pas la taille du tableau.

La structure retournée est quant à elle directement récupérée en tant que valeur de retour de la fonction d'interface programmatique.

Générez et compilez l'extension. Relancez directement votre application de test : le comportement ne devrait pas changer.

Usages

Comme mentionné précédemment, les fonctions sont la porte d'entrée de la collaboration entre extensions. Et nous allons ici voir sur quelques exemples (non exhaustifs) comment peut s'implémenter une telle collaboration.

Réutilisation

Cette utilisation est la plus basique de toute : il s'agit de reproduire dans les extensions ce que les fonctions du C ou du C++ font. Il s'agit d'éclater le traitement d'une extension en plusieurs morceaux, ces morceaux pouvant rester privés (en étant définis en tant que fonctions C/C++ dans l'extension) ou mis à disposition des autres extensions (en étant définis comme fonctions d'extension).

Les développeurs ayant largement l'habitude de ce mécanisme, il ne sera pas présenté ici.

Isolation mémoire des types et structures

Les structures semblaient dans le didacticiel les présentant relativement inutiles. De même, les types extensions semblaient cantonnés à leur extension. Il manquait clairement quelque chose pour les rendre manipulable depuis d'autres extensions. Ce manque est comblé par les fonctions d'extension : autant les types et structures ont une partie opaque aux autres extensions, autant les fonctions n'ont pas ce défaut. Et les fonctions peuvent manipuler les types et structures !

Modifiez le code de l'extension :

PLUGIN func

DEFINE

STRUCT func.s
%{
	std::string _a;
	std::string _b;
%}
delete default: %{}
copy default: %{}

FUNCTION func.new STR 2 -> $func.s
%{
	auto a = ARGV_VALUE(0,string);
	auto b = ARGV_VALUE(1,string);
	return NEW_STRUCT(func,s,(new struct_s { RAW_STRING(a), RAW_STRING(b) }));
%}

FUNCTION func.swap MUTABLE $func.s
%{
	auto s = ARGV_STRUCT(0,func,s);
	std::swap(s->_a,s->_b);
%}

FUNCTION func.push MUTABLE $func.s STR -> STR
%{
	auto s = ARGV_STRUCT(0,func,s);
	auto f = ARGV_VALUE(1,string);
	auto r = s->_b;
	s->_b = s->_a;
	s->_a = RAW_STRING(f);
	return NEW_VALUE(string,NEW_STRING(r));
%}

FUNCTION func.write $func.s VARIABLE -> PTR
%{
	auto s = ARGV_STRUCT(0,func,s);
	SVM_Kernel k = ARGV_VARIABLE(1);
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal__raw(svm,z,STRING,2);
	SVM_Value_Pointer p = ::svm_memory_allocate(svm,k,z);
	SVM_Value *v = ::svm_value_array_new(svm,2);
	v[0] = ::svm_value_string_new__buffer(svm,s->_a.c_str(),s->_a.size());
	v[1] = ::svm_value_string_new__buffer(svm,s->_b.c_str(),s->_b.size());
	::svm_memory_write_pointer(svm,k,p,v);
	return p;
%}

Générez et compilez l'extension. Maintenant, ouvrez un autre fichier d'extension, et ajoutez-y ce code :

PLUGIN other

code:
%{
	static SVM_Structure _s = nullptr;
%}

initialisation:
%{
	SVM_Parameter *p = ::svm_parameter_array_new(svm,2);
	p[0] = ::svm_parameter_value_new(svm,::svm_value_string_new__raw(svm,""));
	p[1] = ::svm_parameter_value_new(svm,::svm_value_string_new__raw(svm,""));
	_s = ::svm_function_call(svm,CONST_PEP(func,new),2,p);
	VARIABLE_GLOBAL(_s);
%}

finalisation:
%{
	VARIABLE_LOCAL(_s);
%}

USE

STRUCT func.s

FUNCTION func.new STR STR -> $func.s

FUNCTION func.push MUTABLE $func.s STR -> STR

FUNCTION func.write $func.s VARIABLE -> PTR

DEFINE

INSTRUCTION other.add STR -> STR
%{
	SVM_Parameter *p = ::svm_parameter_array_new(svm,2);
	p[0] = ::svm_parameter_structure_new(svm,_s);
	p[1] = argv[0];
	return ::svm_function_call(svm,CONST_PEP(func,push),2,p);
%}

INSTRUCTION other.snapshot -> PTR
%{
	SVM_Parameter *p = ::svm_parameter_array_new(svm,2);
	p[0] = ::svm_parameter_structure_new(svm,_s);
	p[1] = ::svm_parameter_variable_new(svm,CURRENT(kernel));
	return ::svm_function_call(svm,CONST_PEP(func,write),2,p);
%}

Générez et compilez l'extension. Maintenant, écrivez une petite application utilisant les deux instructions :other.add et :other.snapshot. Ici, l'extension other a pu manipuler la structure au travers de fonction sans avoir à connaître la structure C/C++ sous-jacente, ce qui garantit une dépendance faible d'implémentation entre les deux extensions.

Polymorphisme inter-extension

Une forme encore plus aboutie de collaboration entre extensions est le polymorphisme inter-extension. Il s'agit ici de faire travailler plusieurs extensions ensemble sans qu'elles le sachent : c'est le code machine qui va orchestrer l'emploi de plusieurs extensions de manière transparente.

Modifiez le code de l'extension initiale :

PLUGIN func

DEFINE

TYPE func.t
%{
	SVM_Value_PluginEntryPoint _type;
	SVM_Structure _s;
%}
delete default:
%{
	VARIABLE_LOCAL(object->_type);
	VARIABLE_LOCAL(object->_s);
%}

INSTRUCTION func.new PEP = VALUE * -> func.t
%{
	SVM_Value_PluginEntryPoint type = ::svm_parameter_value_get(svm,argv[0]);
	::svm_variable_scope_set_shared(svm,type);
	VARIABLE_GLOBAL(type);
	SVM_Structure s = ::svm_function_call(svm,::svm_value_pluginentrypoint_new_prefix(svm,type,"new"),argc-2,argv+2);
	VARIABLE_GLOBAL(s);
	return NEW_PLUGIN(func,t,(new type_t { type, s }));
%}

INSTRUCTION func.apply func.t PEP = VALUE * -> func.t
%{
	auto t = ARGV_PLUGIN(0,func,t);
	argv[0] = ::svm_parameter_structure_new(svm,t->_s);
	SVM_Structure s = ::svm_function_call(svm,::svm_value_pluginentrypoint_new_prefix(svm,t->_type,"apply"),argc,argv);
	VARIABLE_GLOBAL(t->_type);
	VARIABLE_GLOBAL(s);
	return NEW_PLUGIN(func,t,(new type_t { t->_type, s }));
%}

INSTRUCTION func.accumulate func.t PEP = VALUE * -> VALUE
%{
	auto t = ARGV_PLUGIN(0,func,t);
	argv[0] = ::svm_parameter_structure_new(svm,t->_s);
	return ::svm_function_call(svm,::svm_value_pluginentrypoint_new_prefix(svm,t->_type,"accumulate"),argc,argv);
%}

FUNCTION func.inc INT -> INT
%{
	auto i = ARGV_VALUE(0,integer);
	return NEW_VALUE(integer,i+1);
%}

FUNCTION func.dec INT -> INT
%{
	auto i = ARGV_VALUE(0,integer);
	return NEW_VALUE(integer,i-1);
%}

FUNCTION func.add INT INT -> INT
%{
	auto a = ARGV_VALUE(0,integer);
	auto i = ARGV_VALUE(1,integer);
	return NEW_VALUE(integer,a+i);
%}

L'extension maintenant définit un type ultra-générique, dont la construction par l'instruction :func.new indique l'implementation à utiliser. Ensuite, elle définit deux fonctions qui vont appliquer un traitement à la structure d'implémentation : :func.apply et :func.accumulate. Les dernières fonctions sont de simples opérations encapsulées dans des fonctions extension.

Notez dans le code de l'extension la présence de la fonction svm_value_pluginentrypoint_new_prefix, qui permet de construire le nom des fonctions à appeler à partir du nom du type : par exemple, le type plugin.entry devient le nom de fonction plugin.new_entry pour la création de la structure.

Il reste maintenant à fournir des implémentations pour la structure fille du type. Modifiez le code de la seconde extension "other" avec ce code :

PLUGIN other

includes:
%{
#include <vector>
%}

DEFINE

STRUCT other.intarray
%{
	std::vector<SVM_Value_Integer> _v;
%}
delete default:
%{
	for(auto& i: object->_v) { VARIABLE_LOCAL(i); }
%}

FUNCTION other.new_intarray INT * -> $other.intarray
%{
	auto v = new struct_intarray;
	for(SVM_Size i=0 ; i<argc ; ++i)
	{
		SVM_Value_Integer ai = ::svm_parameter_value_get(svm,argv[i]);
		VARIABLE_GLOBAL(ai);
		v->_v.push_back(ai);
	}
	return NEW_STRUCT(other,intarray,v);
%}

FUNCTION other.apply_intarray $other.intarray PEP = VALUE * -> $other.intarray
%{
	auto s = ARGV_STRUCT(0,other,intarray);
	SVM_Value_PluginEntryPoint func = ::svm_parameter_value_get(svm,argv[1]);
	auto rv = new struct_intarray;
	for(const auto& v:s->_v)
	{
		argv[2] = ::svm_parameter_value_new(svm,v);
		SVM_Value_Integer i = ::svm_function_call(svm,func,argc-2,argv+2);
		VARIABLE_GLOBAL(i);
		rv->_v.push_back(i);
	}
	return NEW_STRUCT(other,intarray,rv);
%}

FUNCTION other.accumulate_intarray $other.intarray PEP = VALUE * -> INT
%{
	auto s = ARGV_STRUCT(0,other,intarray);
	SVM_Value_PluginEntryPoint func = ::svm_parameter_value_get(svm,argv[1]);
	SVM_Value_Integer a = NEW_VALUE(integer,0);
	for(const auto& v:s->_v)
	{
		argv[1] = ::svm_parameter_value_new(svm,a);
		argv[2] = ::svm_parameter_value_new(svm,v);
		a = ::svm_function_call(svm,func,argc-1,argv+1);
	}
	return a;
%}

STRUCT other.stringarray
%{
	std::vector<SVM_Value_String> _v;
%}
delete default:
%{
	for(auto& i: object->_v) { VARIABLE_LOCAL(i); }
%}

FUNCTION other.new_stringarray STR * -> $other.stringarray
%{
	auto v = new struct_stringarray;
	for(SVM_Size i=0 ; i<argc ; ++i)
	{
		SVM_Value_String as = ::svm_parameter_value_get(svm,argv[i]);
		VARIABLE_GLOBAL(as);
		v->_v.push_back(as);
	}
	return NEW_STRUCT(other,stringarray,v);
%}

FUNCTION other.apply_stringarray $other.stringarray PEP = VALUE * -> $other.stringarray
%{
	auto s = ARGV_STRUCT(0,other,stringarray);
	SVM_Value_PluginEntryPoint func = ::svm_parameter_value_get(svm,argv[1]);
	auto rv = new struct_stringarray;
	for(const auto& v:s->_v)
	{
		argv[2] = ::svm_parameter_value_new(svm,v);
		SVM_Value_String i = ::svm_function_call(svm,func,argc-2,argv+2);
		VARIABLE_GLOBAL(i);
		rv->_v.push_back(i);
	}
	return NEW_STRUCT(other,stringarray,rv);
%}

FUNCTION other.accumulate_stringarray $other.stringarray PEP = VALUE * -> STR
%{
	auto s = ARGV_STRUCT(0,other,stringarray);
	SVM_Value_PluginEntryPoint func = ::svm_parameter_value_get(svm,argv[1]);
	SVM_Value_String a = ::svm_value_string_new__raw(svm,"");
	for(const auto& v:s->_v)
	{
		argv[1] = ::svm_parameter_value_new(svm,a);
		argv[2] = ::svm_parameter_value_new(svm,v);
		a = ::svm_function_call(svm,func,argc-1,argv+1);
	}
	return a;
%}

FUNCTION other.concat STR STR -> STR
%{
	auto l = ARGV_VALUE(0,string);
	auto r = ARGV_VALUE(1,string);
	std::string rs = RAW_STRING(l)+RAW_STRING(r);
	return NEW_VALUE(string,NEW_STRING(rs));
%}

Cette extension est en trois parties : les deux premières fournissent chacune une implémentation pour le type func.t, et la dernière ajoute une fonction de concaténation de chaines de caractères.

Générez et compilez les deux extensions. Puis, ouvrez un fichier d'application et ajoutez-y ce code :

#!/usr/bin/env svm
LOG
LOCAL PLUGIN "svmpluginfunc/libsvmfunc.so"
LOCAL PLUGIN "svmpluginother/libsvmother.so"
PLUGIN "svmcom.so"
PROCESS "main"
	CODE "main" INLINE
		:memory func.t/t, func.t/tt, INT/i, STR/s
		:func.new other.intarray = 1 2 3 4 5 -> &t
		:func.apply @&t func.inc = -> &tt
		:func.accumulate @&tt func.add = -> &i
		:com.message @&i
		:func.new other.stringarray = "a" "b" "c" "d" "e" -> &t
		:func.apply @&t other.concat = "_1 " -> &tt
		:func.accumulate @&tt other.concat = -> &s
		:com.message @&s
	END
END

Lancez l'application :

./functions.svm
20
a_1 b_1 c_1 d_1 e_1 

Les deux instructions de modification du type arrivent à travailler de manière transparente :

  1. sur le type des données traitées et leur stockage,
  2. sur les opérations qui sont appliquées aux valeurs à l'intérieur des structures.

Et tout cela, au sein des mêmes instructions, le tout contrôlé depuis le code machine. Notez que le comportement présenté ici est exactement celui qu'adopte le type com.device pour se comporter comme une interface de périphérique standardisée.

Conclusion

Vous venez de voir comment créer et manipuler les fonctions extensions depuis une extension.

Les fonctions apportent énormément de capacités aux extensions en leur permettant de travailler ensemble sans forcément avoir de dépendance forte entre elles.

Elles redonnent aussi un sens à l'existence des structures extensions, qui participent à leur manière à cette collaboration.

Pour un concepteur d'extension, une maîtrise approfondie des fonctions extension peut donner une véritable généricité à son extension, sans pour autant sacrifier sa simplicité d'utilisation.