14 KiB
19. Contrôle de flux
19.1 if / elseif / else — V1 REQUIS — FIGÉ
Les conditions sont obligatoirement de type bool. Saselang n'a aucune notion de truthiness/falsiness implicite.
Les parenthèses autour de chaque condition et les accolades autour de chaque bloc sont obligatoires, y compris pour un bloc contenant un seul statement.
La seule chaîne conditionnelle canonique est :
if (condition) {
action();
} elseif (otherCondition) {
otherAction();
} else {
fallback();
}
La forme else if n'existe pas en Saselang. elif n'est pas un alias de elseif.
Un if sans emit est uniquement une structure de contrôle et ne produit aucune valeur.
Un if qui contient emit devient un if producteur de valeur. Dans ce cas :
- le résultat du
ifdoit obligatoirement être affecté à une destination ; - un bloc
elsefinal est obligatoire ; - chaque voie de terminaison normale de chaque branche doit produire explicitement une valeur via
emit; - une voie que le compilateur prouve comme terminant définitivement le contrôle (
return,throw,faultexplicite ou non-terminaison prouvée) n'a pas à exécuteremit; - les valeurs émises doivent être compatibles avec le type de la destination ;
- aucune valeur de secours n'est implicite, y compris pour
Option<T>.
Exemple :
int32 result = if (x > 10) {
emit 1;
} elseif (x > 5) {
emit 2;
} else {
emit 3;
};
Pour un Option<T>, l'absence doit également être explicitement produite :
Option<uint32> result = if (condition) {
emit Option::Some(42);
} else {
emit Option::None;
};
Une branche qui termine définitivement le contrôle ne rejoint pas l'affectation de sortie et n'a donc pas à produire de valeur. Cette règle améliore la preuve de contrôle de flux sans introduire de valeur implicite.
19.2 match — V1 REQUIS — FIGÉ
match remplace switch/case pour la sélection par valeur ou structure.
Forme générale :
match (value) {
pattern1 => {
statements;
}
pattern2 => {
statements;
}
_ => {
statements;
}
}
L'expression entre parenthèses est évaluée exactement une fois. Le match examine ensuite cette valeur selon les patterns déclarés ; il ne réévalue pas l'expression pour chaque branche.
Dans une branche, tout ce qui constitue syntaxiquement le pattern se situe avant =>. => est un séparateur grammatical, pas un opérateur.
Les patterns d'un même match ne doivent pas se chevaucher. Une valeur possible ne peut donc correspondre qu'à une seule branche. L'ordre des branches ne sert pas à résoudre des chevauchements : un chevauchement détectable est une erreur de compilation.
Le match doit être exhaustif :
- si les patterns explicites couvrent toutes les valeurs possibles, une branche finale
_est interdite car elle serait inaccessible ; - si les patterns explicites ne couvrent pas toutes les valeurs possibles, une branche finale
_est obligatoire ; - lorsqu'elle existe,
_est nécessairement la dernière branche.
_ signifie alors « toutes les valeurs restantes non couvertes par les autres patterns ».
Il n'existe ni case, ni default, ni fallthrough. break ne sert pas à quitter un match ; il reste un contrôle de boucle.
Le matching n'exécute pas de code utilisateur caché, de prédicat arbitraire ou de conversion implicite pour décider si une branche correspond. Un pattern décrit directement une valeur, un ensemble de valeurs ou une structure que le compilateur peut examiner à partir de la valeur déjà évaluée.
Les classes ne constituent pas des patterns de sélection dynamique. Une relation de sous-typage comme Dog is Animal créerait naturellement des ensembles chevauchants et contredirait la règle de non-chevauchement. Les tests dynamiques de classe utilisent is avec if/elseif/else.
Une classe peut néanmoins être la valeur capturée par un binding lorsque le type statique d'un payload ou d'un champ est déjà cette classe ; cela n'effectue alors aucun test dynamique de classe.
19.3 Patterns — V1 REQUIS — FIGÉ EN PRINCIPE
Les catégories de patterns déjà figées sont :
literal pattern
enum variant pattern
tuple pattern
struct pattern
typed binding pattern
wildcard _
alternative pattern avec |
nested pattern
Range pattern si et seulement si le Range est valide selon les règles de Range
Les constantes nommées et les détails de compatibilité des littéraux restent à harmoniser avec les règles générales des constantes et des types ; ils ne doivent pas introduire d'évaluation utilisateur cachée.
19.3.1 Alternatives avec |
Dans un pattern, | est un séparateur grammatical d'alternatives et non l'opérateur booléen || ni l'opérateur bitwise |.
1 | 2 | 3 => {
...
}
Les alternatives d'une même branche doivent elles-mêmes être non chevauchantes, et elles ne doivent chevaucher aucune autre branche.
Lorsque des alternatives créent des bindings, toutes les alternatives doivent créer exactement les mêmes bindings, avec les mêmes noms et les mêmes types :
A(uint32 value) | B(uint32 value) => {
use(value);
}
est admissible si A et B sont des variantes distinctes compatibles, alors que les formes où le type ou l'ensemble des bindings change entre alternatives sont interdites.
19.3.2 Enum patterns
Une variante sans payload peut être utilisée directement :
Color::Red => {
...
}
Une variante avec payload peut contenir des sous-patterns. Une capture est un binding typé explicite :
Result::Ok(uint32 value) => {
use(value);
}
Result::Err(Error error) => {
handle(error);
}
La forme Result::Ok(value) n'introduit pas implicitement le type de value : les variables créées par un pattern respectent la règle générale de typage explicite.
_ peut être utilisé comme sous-pattern pour ignorer explicitement un payload ou un composant :
Result::Ok(_) => {
...
}
19.3.3 Tuple patterns
Un tuple peut être décomposé positionnellement en sous-patterns de même arité :
(0, 0) => {
origin();
}
(int32 x, 0) => {
horizontal(x);
}
(int32 x, int32 y) => {
other(x, y);
}
Les bindings sont typés explicitement et leur scope est limité au bloc de leur branche.
19.3.4 Struct patterns
Un struct se déstructure nominalement par ses champs. Le nom du champ est placé à gauche de : et le sous-pattern appliqué à sa valeur à droite :
Point {
x: 0,
y: int32 vertical
} => {
use(vertical);
}
Tous les champs du struct doivent être mentionnés exactement une fois dans le pattern. Il n'existe pas de .. implicite pour ignorer les champs restants. Un champ ignoré doit l'être explicitement avec _ :
Point {
x: 0,
y: _
}
L'ordre des champs n'est pas sémantique puisque les noms les identifient.
La destructuration respecte les règles de visibilité normales. match ne permet jamais d'accéder à un champ inaccessible depuis le contexte courant.
19.3.5 Bindings, scopes et imbrication
Un binding créé par un pattern constitue une déclaration locale typée et obéit aux règles normales de non-shadowing.
Deux branches différentes peuvent employer le même nom de binding parce que leurs scopes sont disjoints, sauf si un symbole de même nom reste déjà visible depuis un scope englobant.
Les patterns sont récursivement imbriquables : tout emplacement qui accepte un sous-pattern peut recevoir un autre pattern compatible avec le type de la sous-valeur.
SomeEnum::PointValue(
Point {
x: 0,
y: int32 y
}
) => {
use(y);
}
Les classes restent exclues comme test de pattern dynamique, même lorsqu'elles apparaissent comme type statique d'un binding.
19.3.6 Guards — REJETÉ
Saselang n'ajoute pas de guard when ou if au pattern.
Une condition métier supplémentaire est écrite explicitement dans le bloc de la branche avec le if normal. Cela évite d'introduire une deuxième phase de sélection, des chevauchements conditionnels runtime et une complexité supplémentaire dans l'analyse d'exhaustivité.
19.4 emit dans if et match — V1 REQUIS — FIGÉ
emit produit explicitement la valeur de la construction productrice courante sans quitter la fonction. Il est distinct de return.
Un if ou un match sans emit est une structure de contrôle et ne produit aucune valeur.
Dès qu'un emit est utilisé dans un if ou un match :
- la construction devient productrice de valeur ;
- cette valeur doit obligatoirement être affectée, soit lors d'une déclaration, soit par une affectation à une destination existante ;
- toutes les voies de terminaison normale de toutes les branches doivent produire explicitement une valeur compatible avec le type de destination ;
- une voie prouvée comme terminant définitivement le contrôle n'a pas à exécuter
emit; - aucune valeur n'est produite implicitement.
Exemples :
int32 result = match (value) {
0 => {
emit 10;
}
_ => {
emit 20;
}
};
int32 result;
result = if (condition) {
emit 1;
} else {
emit 2;
};
Le rôle du compilateur s'arrête à la validité de l'affectation et du contrôle de flux. Il n'est pas requis que la variable destination soit ensuite effectivement utilisée.
19.5 return, break, continue, yield — V1 REQUIS / RÉSERVÉ
return V1 requis
break V1 requis
continue V1 requis
yield réservé pour generators/coroutines si non finalisé en V1
break; et continue; sont des statements terminés par ;. Les labels de boucle ne font pas partie du noyau V1 actuellement retenu.
19.6 Boucles — V1 REQUIS — FIGÉ
Saselang fournit exactement les formes générales suivantes pour les boucles de base :
while (condition) {
...
}
dowhile (condition) {
...
}
for (initialization; condition; update) {
...
}
foreach (Type item in iterable) {
...
}
Pour toutes ces constructions, les parenthèses d'en-tête et les accolades du corps sont obligatoires.
Les conditions de while, dowhile et for sont obligatoirement de type bool.
while évalue sa condition avant chaque itération.
dowhile exécute le corps au moins une fois puis évalue sa condition après chaque itération, malgré la position syntaxique de la condition avant le bloc :
dowhile (condition) {
statements;
}
La forme historique do { ... } while (...); n'existe pas. do reste reserved-foreign et interdit comme identifiant.
19.6.1 for
La condition centrale du for est obligatoire. La forme :
for (;;) {
...
}
est interdite. Le compilateur doit orienter vers :
while (true) {
...
}
pour une boucle inconditionnelle.
Les zones initialization et update peuvent contenir plusieurs éléments séparés par des virgules :
for (i = 0, j = 5; i < x || j < y; i += 1, j += 1) {
...
}
Dans ce contexte, la virgule est un séparateur grammatical de la liste d'initialisation ou de mise à jour ; Saselang n'introduit pas d'opérateur virgule général.
Les virgules finales restent interdites. La condition centrale reste une seule expression booléenne, même lorsque les zones d'initialisation ou de mise à jour contiennent plusieurs éléments.
La liste exacte des formes de statement admises dans les zones d'initialisation et de mise à jour doit rester cohérente avec la grammaire générale des statements ; aucune expression pure ne reçoit implicitement un effet de bord.
19.6.2 foreach
La forme canonique est :
foreach (Type item in iterable) {
...
}
Le type du binding est explicite ; foreach (item in iterable) sans type n'est pas admis.
in est un mot-clé Saselang actif dans cette construction.
foreach s'appuie sur les interfaces Core Iterable<T> / Iterator<T>. La variable d'itération appartient au scope du bloc de boucle.
19.6.3 Formes redondantes rejetées
Saselang n'ajoute pas de synonymes de boucle ou de condition lorsqu'une construction existante exprime déjà la même sémantique :
until (...) -> utiliser while (!...)
repeat ... until -> utiliser dowhile (!...)
unless (...) -> utiliser if (!...)
loop { ... } -> utiliser while (true) { ... }
Ces mots peuvent rester reserved-foreign pour éviter les collisions avec d'autres langages, sans devenir des mots-clés actifs Saselang.
19.7 Statements, blocs et terminateurs — V1 REQUIS — FIGÉ
Le point-virgule ; marque exclusivement la fin d'un statement ou d'une déclaration sans corps qui exige un terminateur. Il n'est jamais un séparateur vide ou décoratif.
Sont interdits :
;
;;
foo();;
Une construction terminée par un bloc { ... } ne reçoit pas de ; supplémentaire. Les accolades ferment déjà le bloc.
Les blocs de contrôle utilisent toujours { ... }. Un bloc anonyme à l'intérieur d'une callable suit la même règle et n'est pas suivi de ;.
Les virgules séparent deux éléments ; Saselang n'autorise pas les trailing commas. Ainsi :
foo(first, second, third); // valide
foo(first, second, third,); // invalide
Cette règle vaut pour les listes grammaticales correspondantes : arguments, paramètres, éléments de tuple/array, paramètres génériques et autres listes définies par la grammaire. Les listes spécifiques de for suivent la même absence de virgule finale.
Les retours à la ligne, espaces, tabs et indentation n'ont aucune signification grammaticale. Hors contenu textuel significatif, une construction peut être placée sur une seule ligne sans changer sa sémantique.
19.8 switch/case — REJETÉ
Pas de switch/case C/Java avec fallthrough. match couvre la sélection structurelle sans case, default ni chute implicite.