Le compilateur suit la syntaxe habituelle des commandes 4D et, en ce sens, ne vous demande aucun comportement particulier dans l’optique de la compilation.
Cette section propose cependant certains rappels et quelques précisions :
Certaines commandes influant sur le type d’une variable peuvent, si vous n’y prêtez pas attention, conduire à des conflits de type.
Certaines commandes admettant des syntaxes ou des paramètres différents, il est préférable de savoir ce qu’il est plus approprié de choisir.
Pour les routines opérant sur les chaînes, seule la fonction Character code réclame une attention plus particulière. Lorsque vous travaillez en mode interprété, vous pouvez indifféremment passer une chaîne non vide ou vide à cette fonction. En compilé, vous ne pouvez pas passer une chaîne vide. Si vous le faites, le compilateur ne peut détecter une erreur à la compilation si l’argument passé à Character code est une variable.
Ces deux commandes permettent d’écrire et de relire des variables envoyées sur disque. On passe des variables en paramètres à ces commandes. Souvenez-vous simplement qu’il faudra toujours relire une variable d’un type dans une variable de même type. Supposons que vous souhaitiez envoyer une liste de variables dans un fichier. Afin de ne pas prendre de risque de changement de type par inattention, nous vous recommandons d’adopter une méthode de travail simple qui consiste à indiquer en début de liste le type des variables envoyées. Ainsi, lorsque vous relirez ces variables, vous commencerez toujours par récupérer cet indicateur dont vous connaissez le type. Ensuite, vous appellerez RECEIVE VARIABLE en toute connaissance de cause par l’intermédiaire d’un Au cas ou.
Exemple :
SET CHANNEL(12;"LeFichier") If(OK=1) $Type:=Type([Client]CA_Cumulé) SEND VARIABLE($Type) For($i;1;Records in selection) $CA_Envoi:=[Client]CA_Cumulé SEND VARIABLE($CA_Envoi) End for End if SET CHANNEL(11) SET CHANNEL(13;"LeFichier") If(OK=1) RECEIVE VARIABLE($Type) Case of
:($Type=Is string var) RECEIVE VARIABLE($LaChaine) `Traitement de la variable reçue
:($Type=Is real) RECEIVE VARIABLE($LeRéel) `Traitement de la variable reçue
:($Type=Is text) RECEIVE VARIABLE($LeTexte) `Traitement de la variable reçue End case End if SET CHANNEL(11)
Field(Ptr_Champ) ou (NoDeTable;NoDeChamp) Table(Ptr_Table) ou (Ptr_Champ) ou (NoDeTable)
Dans l’optique du compilateur, ces deux commandes ne comportent rien de spécifique. Nous appelons simplement votre attention sur un cas pratique : ces deux commandes retournent des résultats de type différent suivant le paramètre qui leur est passé :
si vous passez un pointeur à la fonction Table, le résultat retourné sera de type Numérique.
si vous passez un Numérique à la fonction Table, le résultat retourné sera de type Pointeur. On comprend aisément que ces deux fonctions ne suffisent pas au compilateur pour déterminer le type du résultat. Dans ce cas, pour qu’il n’y ait aucune ambiguïté, utilisez une directive de compilation.
L’expression “25 modulo 3” peut s’écrire de deux façons différentes dans 4D :
LaVariable:=Mod(25;3)
ou
LaVariable:=25%3
Il existe pour le compilateur une différence entre ces deux écritures : Mod s’applique à tous les types de numériques tandis que l’opérateur % s’applique exclusivement aux Entiers et Entiers longs (si les opérandes de l’opérateur % dépassent les limites des Entiers longs, le résultat renvoyé sera probablement faux).
Pour la gestion des interruptions, le langage de 4D dispose de la commande IDLE. Cette commande devra être utilisée lorsque vous vous servirez de la commande ON EVENT CALL.
On pourrait définir cette commande comme une directive de gestion des événements. Seul le noyau de 4D peut détecter un événement Système (clic souris, action sur le clavier…). Dans la plupart des cas, des appels au noyau sont lancés par le code compilé lui-même, de façon transparente pour vous.
En revanche, dans le cas où vous attendez un événement sans rien faire, comme dans une boucle d’attente, il est bien évident qu’aucun appel n’est effectué.
`Méthode projet ClicSouris If(MouseDown=1) <>vTest:=True ALERT("Quelqu’un a cliqué avec la souris") End if
`Méthode projet Attente <>vTest:=False ON EVENT CALL("ClicSouris") While(<>vTest=False) `Boucle d’attente de l’événement End while ON EVENT CALL("")
Dans ce cas, vous ajouterez la directive IDLE de la façon suivante :
`Méthode projet Attente <>vTest:=False ON EVENT CALL("ClicSouris") While(<>vTest=False) IDLE `Appel au noyau pour discerner un événement End while ON EVENT CALL("")
Cette commande ne doit être utilisée que dans des méthodes projet d’interception d’erreurs. Cette commande fonctionne comme en mode interprété, sauf dans une méthode ayant été appelée par l’une des commandes suivantes : EXECUTE FORMULA, APPLY TO SELECTION et _o_APPLY TO SUBSELECTION. Il convient d’éviter cette situation.
COPY ARRAY admet deux paramètres de type Tableau. Lorsque le compilateur rencontre cette commande pendant le typage et que l’un des paramètres tableau n’est pas déclaré ailleurs, le compilateur déduit le type du tableau non déclaré suivant le type de celui qui l’est. Cette déduction est faite dans les deux cas suivants :
le tableau typé ailleurs est le premier paramètre. Le compilateur donne au second tableau le type du premier.
le tableau déclaré est le second paramètre. Dans ce cas, le compilateur donne au premier tableau le type du second.
Le compilateur étant rigoureux sur les types, un COPY ARRAY ne peut se faire que d’un tableau d’un type vers un tableau de même type. En conséquence, si vous souhaitez faire une copie d’un tableau d’éléments de types voisins, c’est-à-dire les Entiers, Entiers longs et Numérique ou les Textes et les Alphas ou les Alphas de longueurs différentes, vous devrez le faire élément par élément.
Imaginez que vous vouliez faire une copie d’un tableau d’Entiers vers un tableau de Numériques. Procédez comme suit :
$Taille:=Size of array(TabEntier) ARRAY REAL(TabRéel;$Taille) `Donnons la même taille au tableau de Réels qu’au tableau d’Entiers For($i;1;$Taille)
TabRéel{$i}:=TabEntier{$i} `Recopions chacun des éléments End for
Souvenez-vous que vous ne pouvez changer le nombre de dimensions d’un même tableau en cours de route. Vous provoqueriez une erreur de typage en copiant un tableau à une dimension dans un tableau à deux dimensions.
De même que pour 4D en mode interprété, ces quatre commandes n’exigent pas de déclaration de tableau. Le tableau non déclaré recevra le même type que le champ spécifié dans la commande. Si vous écrivez :
SELECTION TO ARRAY([LaTable]ChampEntier;LeTableau)
le type de LeTableau sera donc Tableau d’Entiers à une dimension.
Dans le cas où le tableau a déjà été déclaré, veillez à ce que les champs soient du même type. Bien qu’Entier, Entier long et Réel soient des types voisins, vous ne pouvez pas supposer qu’il soient équivalents. Vous avez en revanche plus de latitude lorsqu’il s’agit des types Texte et Alpha. Par défaut, lorsqu’un tableau n’a pas été déclaré au préalable et que vous appliquez l’une de ces commandes avec pour paramètre un champ de type Alpha, le type attribué au tableau sera Texte. Si le tableau a été déclaré auparavant comme étant de type Alpha ou de type Texte, ces commandes respecteront vos directives.
les tableaux Texte à une dimension. Ces deux commandes n’exigent pas la déclaration du tableau qui leur est passé en paramètre. Par défaut, un tableau non déclaré recevra le type Texte. Si le tableau a été déclaré auparavant comme étant de type Alpha ou de type Texte, ces commandes respecteront vos directives.
Le compilateur ne peut pas, durant le typage ou la compilation, détecter un conflit de type dans le cas où vous utiliseriez des pointeurs dépointés comme paramètre de commande de déclaration d’un tableau. Si vous écrivez :
SELECTION TO ARRAY([LaTable]LeChamp;LePointeur->)
où LePointeur-> représente un tableau, le compilateur ne peut vérifier que le type du champ et du tableau sont identiques. Il vous appartient d’éviter alors ce type de conflit.
Pour cela, le compilateur vous fournit une aide précieuse : les Warnings. Chaque fois qu’il rencontre une routine de déclaration de tableau dans laquelle l’un des paramètres est un pointeur, il vous délivre un message vous invitant à la vigilance.
Si vous souhaitez compiler une base de données qui utilise des tableaux locaux (tableaux visibles uniquement par les méthodes qui les ont créés), il est nécessaire de les déclarer explicitement dans 4D avant de les utiliser.
La déclaration explicite d’un tableau signifie l’utilisation d’une commande de type ARRAY REAL, ARRAY INTEGER, etc.
Par exemple, si une méthode génère un tableau local d’entiers contenant 10 valeurs, vous devez écrire au préalable la ligne suivante :
Get pointer est une fonction qui retourne un pointeur sur le paramètre que vous lui avez passé. Supposons que vous vouliez initialiser un tableau de pointeurs. Chacun des éléments de ce tableau pointe vers une variable donnée. Ces variables sont au nombre de douze et nous les appelons V1, V2, …V12. Vous pourriez écrire :
ARRAY POINTER(Tab;12)
Tab{1}:=->V1
Tab{2}:=->V2
Tab{12}:=->V12
Vous pouvez aussi écrire :
ARRAY POINTER(Tab;12) For($i;1;12)
Tab{$i}:=Get pointer("V"+String($i)) End for
A la fin de l’exécution de cette séquence, vous récupérez un tableau de pointeurs dont chaque élément pointe sur une variable Vi.
Ces deux séquences sont bien entendu compilables. Toutefois, si les variables V1 à V12 ne sont pas utilisées explicitement ailleurs dans la base, le compilateur ne pourra pas les typer. Il vous faut donc les nommer ailleurs explicitement. Cette déclaration explicite peut se faire de deux façons :
soit en déclarant V1, V2, …V12 par une directive de compilation :
C_LONGINT(V1;V2;V3;V4;V5;V6;V7;V8;V9;V10;V11;V12)
soit en affectant ces variables dans une méthode :
Chaque variable de la base compilée n’ayant qu’un seul type, la fonction Type peut sembler d’un intérêt limité. Elle est cependant très précieuse lorsqu’on travaille avec des pointeurs. En effet, il peut être nécessaire de connaître le type de la variable pointée par un pointeur, la souplesse des pointeurs faisant que l’on ne sait pas toujours sur quoi ils pointent.
La commande EXECUTE FORMULA, historique dans 4D, présente des avantages en mode interprété qui n’ont pas grand sens en mode compilé. En effet, en mode compilé, le nom de la méthode passé en paramètre à cette commande sera simplement interprété. Vous ne bénéficierez donc pas de tous les avantages apportés par le compilateur, sans compter que la syntaxe de votre paramètre ne pourra pas être vérifiée. De surcroît, vous ne pouvez pas lui passer des variables locales comme paramètres. On peut avantageusement remplacer un EXECUTE FORMULA par une série d’instructions. Nous vous en donnons ici deux exemples.
Ici, l’EXECUTE FORMULA peut être facilement remplacé par un Au cas ou :
Case of
:($Num=1) Impri1
:($Num=2) Impri2
:($Num=3) Impri3 End case
La commande EXECUTE FORMULApeut toujours être avantageusement remplacée. En effet, la méthode à exécuter est prise dans la liste des méthodes projet de la base ou des commandes 4D, qui sont en nombre limité. En conséquence, il est toujours possible de remplacer la commande EXECUTE FORMULAsoit par un Au cas ou, soit par une autre commande.
Ces deux commandes, précieuses en mode interprété, n’ont pas de fonction en mode compilé. Vous pouvez cependant les laisser dans vos méthodes : TRACE et seront simplement ignorées par le compilateur.
Compte tenu du processus de typage par le compilateur, une variable ne peut à aucun moment être indéfinie en mode compilé. En effet, toutes les variables ont une existence dès la fin de la compilation. La fonction Undefined retourne donc toujours Faux, quel que soit le paramètre que vous lui passez.
Note : Pour savoir si une application tourne en mode compilé, utilisez la fonction Is compiled mode.
En mode interprété, après l’exécution d’un LOAD VARIABLES, vous pouvez savoir si le document existe en testant si l’une des variables, théoriquement lue, est indéfinie ou non. Ceci n’est bien évidemment plus possible avec le compilateur, puisque la fonction Undefined renvoie toujours Faux.
La méthode la plus simple pour réaliser cette opération en mode interprété comme en mode compilé est la suivante : 1. Initialisez les variables que vous allez recevoir à une valeur dont vous êtes sûr qu’elles ne sont pas initialisées. 2. Après le LOAD VARIABLES, comparez l’une des variables reçues à la valeur d’initialisation. La méthode s’écrira alors de la façon suivante :
Var1:="xxxxxx" `"xxxxxx" est une valeur qui ne peut pas être ramenée par un LIRE VARIABLES Var2:="xxxxxx" Var3:="xxxxxx" Var4:="xxxxxx" LOAD VARIABLES("LeDocument";Var1;Var2;Var3;Var4) If(Var1="xxxxxx") `Le document n’a pas été trouvé
Cette routine admet deux syntaxes différentes en mode interprété : CLEAR VARIABLE(LaVariable) CLEAR VARIABLE("a") En mode compilé, la première syntaxe, CLEAR VARIABLE(LaVariable), provoque non la destruction de la variable, mais sa réinitialisation (remise à zéro pour un numérique, chaîne vide pour une chaîne de caractères ou un texte…), aucune variable ne pouvant être indéfinie en mode compilé. En conséquence, CLEAR VARIABLE ne libère pas de mémoire en mode compilé sauf dans quatre cas : les variables de type Texte, Image, BLOB et les tableaux. Pour un tableau, CLEAR VARIABLE équivaut à une nouvelle déclaration du tableau ou les tailles sont remises à zéro.
Pour un tableau LeTableau dont les éléments sont de type Entier, CLEAR VARIABLE(LeTableau) équivaut à l’une des expressions suivantes :
ARRAY INTEGER(LeTableau;0) `s’il s’agit d’un tableau à une dimension ARRAY INTEGER(LeTableau;0;0) `s’il s’agit d’un tableau à deux dimensions
La seconde syntaxe, CLEAR VARIABLE("a"), n’est pas compatible avec le compilateur puisque celui-ci accède aux variables non par leur nom, mais par leur adresse mémoire.
Les commandes suivantes ont un point commun : elles admettent toutes un premier paramètre [LaTable] facultatif alors que le second paramètre peut être un pointeur.
Il est parfaitement possible en mode compilé de garder ce caractère optionnel au paramètre [LaTable]. Le compilateur fait toutefois une supposition dans le cas où le premier paramètre passé à l’une de ces commandes est un pointeur. Ne pouvant pas savoir sur quoi pointe ce pointeur, il suppose qu’il s’agit d’un pointeur de Table.
Prenons par exemple le cas de la commande QUERY dont la syntaxe est la suivante : QUERY({LaTable{;LaFormule{;*}}}) Le premier élément du paramètre LaFormule doit être un champ. Si vous écrivez :
QUERY(LePtrChamp->=True)
le compilateur va chercher en deuxième élément de formule un symbole représentant un champ. Or, il trouvera le signe "=". Il délivrera un message d’erreur, ne pouvant identifier la commande avec une expression qu’il sait traiter.
Les commandes dont le premier paramètre [laTable] est facultatif et dont le second paramètre est également facultatif présentent une particularité lors de l'utilisation de pointeurs. Dans ce contexte, pour des raisons internes l'utilisation en tant que paramètre d'une commande retournant un pointeur (par exemple Current form table) n'est pas autorisée par le compilateur (une erreur est générée).
C'est le cas, par exemple, de la commande FORM SCREENSHOT. Le code suivant fonctionnera en mode interprété mais sera rejeté en cas de compilation :
//provoque une erreur à la compilation FORM SCREENSHOT(Current form table->;$nomForm;$monImage)
Dans ce cas, il suffit d'utiliser une variable intermédiaire afin que le même code soit validé par le compilateur :
//code équivalent compilable C_POINTER($ptr) $ptr:=Current form table FORM SCREENSHOT($ptr->;$nomForm;$monImage)
Si vous créez vos propres ressources 4DK# (constantes), assurez-vous que les numériques soient de type Entier long (L) ou Réel (R) et les alphas de type Chaîne (S). Tout autre type génèrera un Warning.