Pawn (langage)
Le PAWN[2] (successeur du Small-C) est un langage de programmation open source utilisé sur différentes plates-formes.
Pawn[1] | ||
Date de première version | 1998 | |
---|---|---|
Paradigme | Procédural, impératif, structuré | |
Développeur | ITB Compuphase | |
Dernière version | 4.0.5749 () | |
Typage | Fort, statique | |
Influencé par | Small-C, C | |
Système d'exploitation | Windows, GNU/Linux, Mac OS X | |
Licence | Libre | |
Site web | (en) « Site de Compuphase » | |
Extensions de fichiers | .pwn, .p, .inc | |
Sa syntaxe est assez proche de celle du langage C bien que le typage soit différent. À défaut de ne pas fournir de pointeurs, les programmes implémentés dans la machine abstraite du PAWN assurent une vérification des indices de tableau ainsi qu'une protection quant au dépassement de la mémoire programme donnée[3].
Utilisations
Le pawn étant un langage adapté pour des machines à ressource limitée, il a été utilisé dans quelques technologies embarquées comme le satellite ESTCube-1[1] ou plus communément pour des serveurs de jeux notamment SA-MP (San Andreas - MultiPlayer)[4].
On citera par ailleurs Eternal Lands qui a été développé en Small-C.
Éléments du PAWN
Comme tout langage de programmation, le PAWN a ses propres spécificités.
main()
{
print("Hello world !");
}
/* Ici main désigne le point d'entrée du programme, il est impératif de le mettre.
Ainsi, dès l'exécution, le programme affichera "Hello World".
*/
Les variables
Les types de variables sont plus restreints qu'en C et leur déclaration est différente.
Effectivement, on va introduire le mot-clé « new » suivi du type (s'il y a) ainsi que le nom de la variable.
Par défaut, les variables sont déclarées comme des entiers, il n'est donc pas nécessaire de préciser un type dans ce cas-là.
Il est préférable d'initialiser chaque variable que l'on déclare afin qu'elle ne contienne pas une valeur erronée même si une valeur par défaut leur est attribuée.
Type | Déclaration | Spécificateur |
---|---|---|
Entier | new a = 3;
| %d, %i |
Flottant | new Float:pi = 3.14;
| %f (%.2f → 2 décimales) |
Booléen | new bool:lightOn = false;
| %b |
Caractère | new lettreB = 'B';
| %c (%d → code ASCII) |
Chaîne de caractères | new monEncyclopedie[] = "Wikipédia";
| %s |
main()
{
new Float:pi = 3.14;
printf("Une valeur approchée de pi est %f", pi);
}
Les structures conditionnelles
On distinguera deux principales structures conditionnelles : « if » ainsi que le « switch ».
Structure « if »
Cette structure se compose du mot-clé « if » précédant une expression booléenne.
Une première forme de cette structure se définit en une simple vérification comme ci-dessous :
main()
{
new a = -4;
if(a < 0)
{
printf("Le nombre est négatif ! (%d)", a);
}
}
Néanmoins, nous pouvons tout aussi gérer la négation de la condition entrée avec le mot-clé « else ».
main()
{
new a = -4;
if(a < 0)
{
printf("Le nombre est négatif ! (%d)", a);
}
else
{
printf("Le nombre est positif ! (%d)", a);
}
}
Un dernier mot-clé qui permet de vérifier une expression si jamais la précédente renvoyait la valeur « faux » : « else if ».
main()
{
new a = -4;
if(a < 0)
{
printf("Le nombre est négatif ! (%d)", a);
}
else if(a == 0)
{
printf("Le nombre est nul !");
}
else
{
printf("Le nombre est strictement positif ! (%d)", a);
}
}
Remarque : en utilisant le « else » à la fin, je vérifie si aucune des conditions le précédant n'ont été remplies.
La structure globale débute, quoi qu'il en soit, par un « if ».
Structure « switch »
La structure « switch » (ou « cas..parmi » en terme algorithmique) est utilisée dans le cas où vous auriez à vérifier successivement les valeurs d'une variable. Vous pourrez utiliser une structure « if » avec un nombre considérable de « else if ».
Cependant, la structure « switch » s'adapte bien à ce genre de cas.
main()
{
new a = 3;
switch(a)
{
case 1:
{
printf("La variable 'a' stocke la valeur 1");
}
case 0, -1:
{
printf("La variable 'a' stocke la valeur 0 ou -1");
}
case 2..5:
{
printf("La variable 'a' stocke une valeur comprise entre 2 et 5");
}
default:
{
printf("La variable 'a' stocke une valeur différente de celles vérifiées précédemment");
}
}
}
Les structures itératives
Une structure itérative (ou boucle) va répéter un certain nombre de fois une ou plusieurs instructions (on parle d'itérations).
On distingue également deux principales structures itératives, chacune caractérisée par un nombre d'itérations déterminé.
Boucle « pour »
La boucle « pour » va conventionnellement exécuter un nombre de fois déterminé les instructions imbriquées dans celle-ci.
On parle alors de boucle déterministe[5].
Dans une telle boucle, on utilisera souvent un incrément. Elle est particulièrement utile quand il faut répéter une instruction plusieurs fois comme un message à afficher ou des calculs successifs.
main()
{
new somme = 0;
for(new i = 0; i < 10; i++)
{
somme += i; // Équivalent à somme = somme + i;
}
printf("La somme des 10 premiers entiers est égale à %d.", somme);
/*
Ici on calcule la somme des 10 premiers entiers.
*/
}
Boucle «while»
La boucle «while» est, quant à elle, une boucle indéterministe.
Elle est généralement guidée par une condition qui ne vérifie pas un nombre d'itérations effectuées.
Une telle boucle ne nécessite pas forcément d'un incrément comme celle présentée ci-dessus.
main()
{
new nb = 17;
while(nb > 0)
{
nb /= 2; // Équivalent à nb = nb / 2;
}
/*
Dans cet exemple, on divise le nombre de départ successivement par 2 jusqu'à ce qu'il atteigne 0.
*/
}
Boucle «do..while»
La boucle «do..while» est, elle aussi, indéterministe.
Contrairement à la boucle «while», elle va exécuter les instructions qu'elles comportent avant de vérifier la condition.
Cela garantit au moins un passage dans la boucle. Elle est particulièrement sollicitée lors de contrôles de saisie utilisateur.
/* Programme calculant la somme des N premiers entiers non nuls (où N est saisi par l'utilisateur) */
main()
{
new n_entiers = 0, sum = 0;
/* Contrôle de la saisie utilisateur */
do
{
printf("Saisissez jusqu'à quel entier la somme s'effectuera (nombre > 1): ");
n_entiers = getvalue(); // La fonction getvalue() récupère la valeur entière saisie par l'utilisateur dans la console
}
while(n_entiers <= 1);
/* Calcul de la somme des entiers consécutifs jusqu'à n_entiers */
for(new i = 0; i <= n_entiers; i++)
sum += i;
/* Affichage des résultats */
printf("La somme des entiers allant de 1 à %d est égale à %d", n_entiers, sum);
}
Les fonctions / procédures
En algorithmique, on distingue souvent la fonction d'une procédure par l'existence ou non d'une valeur de retour. En C, la distinction s'effectue par le mot-clé mis devant la définition de la fonction (void ou un type de données). Le PAWN applique cependant une confusion entre les deux termes puisqu'il ne permet pas d'établir explicitement la différence entre ces deux notions.
Déclaration d'une fonction
Ainsi, la déclaration d'une fonction en PAWN se fait de la manière suivante :
main()
{
/* Saisies de l'utilisateur */
new a = getvalue();
new b = getvalue();
/* Affichage du résultat */
printf("La multiplication de a par b retourne : %d", multiplie(a, b)); // Appel de la fonction créée
}
multiplie(a, b) // Multiplie un entier a par b
{
return a * b; // On invoque ici le mot-clé return afin d'attribuer une valeur de retour à la fonction.
}
De même, si l'on souhaite renvoyer une donnée d'un type différent de l'entier il suffira de préciser le type devant la déclaration :
bool:AreEquals(a, b) // La fonction renvoie ici vrai ou faux
{
return a == b; // Renvoie la valeur du test d'égalité
}
Il sera cependant nécessaire de préciser que la fonction renvoie un type différent de celui par défaut : l'entier. De ce fait, il faudra créer le prototype (ou encore entête) de votre fonction avant de l'appeler. On aura ainsi :
forward bool:AreEquals(a, b); // Création d'une entête en invoquant le mot-clé forward
main()
{
new a = 5, b = 3;
printf("A et B égaux ? %b\n", AreEquals(a, b));
}
bool:AreEquals(a, b)
{
return a == b;
}
Passage de paramètres
Une fonction peut présenter (ou non) des arguments dans la déclaration de sa fonction. Le langage Pawn propose deux types pour transmettre des paramètres à la fonction.
Le premier étant le passage de paramètres par valeur qui a la particularité de créer une copie de la variable passée en paramètre lors de l'appel de la fonction. Une telle copie ne peut donc affecter la valeur de la variable de départ.
main()
{
new a = 2;
square(a);
printf("a vaut maintenant : %d", a); // a vaudra 2, avec un tel passage de paramètre
}
square(value)
{
value *= value; // On ne retourne pas de résultat ici, on essaie d'agir directement sur la variable.
}
Ce passage de paramètres est favorisé pour des traitements sur la valeur d'une variable ou sa lecture. Néanmoins, si l'on veut modifier la valeur de la variable passée en paramètre il faudra favoriser le second passage de paramètres : par référence. Un tel passage implique la transmission à la fonction non pas de la valeur de la variable mais de son adresse mémoire permettant donc d'agir sur la variable en mémoire sans passer par une duplication de celle-ci[6].
main()
{
new a = 2;
square(a);
printf("a vaut maintenant : %d", a); // a vaudra 4 en ayant changé le type de passage de paramètre
}
square(&value) // Il suffira d'ajouter un & qui récupèrera l'adresse mémoire de la variable et non pas la donnée qu'elle stocke.
{
value *= value;
}
Ce type de passage est largement favorisé quand on est contraint de retourner plus d'une valeur (exemple : fonction qui convertit le nombre de secondes en hh-mm-ss)[6].
Notes et références
- Portail de l’informatique