3. Syntaxe
Dans cette section, la syntaxe globale de CDDL est présentée, accompagnée de quelques exemples illustrant simplement la syntaxe. (La définition ne tente pas d'être trop formelle ; référez-vous à l'Annexe B pour les détails.)
3.1. Conventions générales
La syntaxe de base est inspirée de l'ABNF [RFC5234], avec les caractéristiques suivantes :
-
Les règles, qu'elles définissent des groupes ou des types, sont définies avec un nom, suivi d'un signe égal "=" et de la définition réelle selon les règles syntaxiques respectives de cette définition.
-
Un nom peut être composé de n'importe quel caractère de l'ensemble {"A" à "Z", "a" à "z", "0" à "9", "", "-", "@", ".", "$"}, commençant par un caractère alphabétique (y compris "@", "", "$") et se terminant par un tel caractère ou un chiffre.
- Les noms sont sensibles à la casse.
- Le style préféré est de commencer un nom par une lettre minuscule.
- Le trait d'union est préféré au trait de soulignement (sauf dans un "mot nu" (Section 3.5.1), où la sémantique peut réellement nécessiter un trait de soulignement).
- Le point peut être utile pour des spécifications plus larges, pour exprimer une certaine structure de module (comme dans "tcp.throughput" vs "udp.throughput").
- Un certain nombre de noms sont prédéfinis dans le prélude CDDL, comme indiqué à l'Annexe D.
- Les noms de règles (types ou groupes) n'apparaissent pas dans l'encodage CBOR réel, mais les noms utilisés comme "mots nus" dans les clés membres le font.
-
Les commentaires commencent par un caractère ";" (point-virgule) et se terminent à la fin d'une ligne (LF ou CRLF).
-
Sauf à l'intérieur des chaînes, l'espace blanc (espaces, nouvelles lignes et commentaires) est utilisé pour séparer les éléments syntaxiques pour la lisibilité (et pour séparer les identifiants, les opérateurs de plage ou les nombres qui se suivent) ; il est sinon totalement optionnel.
-
Les nombres hexadécimaux sont précédés de "0x" (sans guillemets) et sont insensibles à la casse. De même, les nombres binaires sont précédés de "0b".
-
Les chaînes de texte sont entourées de caractères guillemets doubles '"'. Elles suivent les conventions pour les chaînes telles que définies dans la Section 7 de [RFC8259]. (Les utilisateurs d'ABNF voudront peut-être noter qu'il n'y a pas de support dans CDDL pour le concept d'insensibilité à la casse dans les chaînes de texte ; si nécessaire, des expressions régulières peuvent être utilisées (Section 3.8.3).)
-
Les chaînes d'octets sont entourées de caractères guillemets simples "'" et peuvent être préfixées par "h" ou "b64". Si elles ne sont pas préfixées, la chaîne est interprétée comme une chaîne de texte, sauf que les guillemets simples doivent être échappés et que les octets UTF-8 résultants sont marqués comme une chaîne d'octets (type majeur 2). Si elle est préfixée par "h" ou "b64", la chaîne est interprétée comme une séquence de paires de chiffres hexadécimaux (base16 ; voir Section 8 de [RFC4648]) ou une chaîne base64(url) (Section 4 ou Section 5 de [RFC4648]), respectivement (comme avec la notation de diagnostic dans la Section 6 de [RFC7049] ; cf. Annexe G.2) ; tout espace blanc présent à l'intérieur de la chaîne (y compris les commentaires) est ignoré dans le cas préfixé.
-
CDDL utilise UTF-8 [RFC3629] pour son encodage. Le traitement de CDDL n'implique pas de processus de normalisation Unicode.
Exemple :
; Ceci est un commentaire
person = { g }
g = (
"name": tstr,
age: int, ; "age" est un mot nu
)
3.2. Occurrence
Un indicateur d'occurrence optionnel peut être donné devant une entrée de groupe. Il s'agit soit (1) de l'un des caractères "?" (optionnel), "" (zéro ou plus), ou "+" (un ou plus), soit (2) de la forme nm, où n et m sont des entiers non signés optionnels et n est la limite inférieure (par défaut 0) et m est la limite supérieure (par défaut pas de limite) des occurrences.
Si aucun indicateur d'occurrence n'est spécifié, l'entrée de groupe doit apparaître exactement une fois (comme si 1*1 était spécifié). Une entrée de groupe avec un indicateur d'occurrence correspond à des séquences de paires nom/valeur qui sont composées en concaténant un nombre de séquences que l'entrée de groupe de base correspond, où le nombre doit être autorisé par l'indicateur d'occurrence.
Notez que CDDL, en dehors de toute directive/annotation qui pourrait éventuellement être définie, ne fait aucune prescription quant à savoir si les tableaux ou les cartes utilisent un encodage à longueur définie ou indéfinie. C'est-à-dire qu'il n'y a pas de corrélation entre laisser la taille d'un tableau "ouverte" dans la spécification et le fait qu'il soit ensuite échangé avec une longueur définie ou indéfinie.
Veuillez également noter que CDDL peut décrire une flexibilité que le modèle de données de la représentation cible n'a pas. C'est assez évident pour JSON mais est également pertinent pour CBOR :
apartment = {
kitchen: size,
* bedroom: size,
}
size = float ; en m2
La spécification précédente ne signifie pas que CBOR est modifié pour permettre l'utilisation de la clé "bedroom" plus d'une fois. En d'autres termes, en raison des restrictions imposées par le modèle de données, la troisième ligne se transforme à peu près en :
? bedroom: size,
(Les indicateurs d'occurrence au-delà de un sont toujours utiles dans les cartes pour les groupes qui autorisent une variété de clés.)
3.3. Noms prédéfinis pour les types
CDDL prédéfinit un certain nombre de noms. Cette sous-section résume ces noms, mais veuillez consulter l'Annexe D pour les définitions exactes.
Les mots-clés suivants pour les types de données primitifs sont définis :
"bool" : Valeur booléenne (type majeur 7, information supplémentaire 20 ou 21).
"uint" : Un entier non signé (type majeur 0).
"nint" : Un entier négatif (type majeur 1).
"int" : Un entier non signé ou un entier négatif.
"float16" : Un nombre représentable comme un flottant de demi-précision [IEEE754] (type majeur 7, information supplémentaire 25).
"float32" : Un nombre représentable comme un flottant de simple précision [IEEE754] (type majeur 7, information supplémentaire 26).
"float64" : Un nombre représentable comme un flottant de double précision [IEEE754] (type majeur 7, information supplémentaire 27).
"float" : L'un de float16, float32 ou float64.
"bstr" ou "bytes" : Une chaîne d'octets (type majeur 2).
"tstr" ou "text" : Une chaîne de texte (type majeur 3).
(Notez qu'il n'y a pas de noms prédéfinis pour les tableaux ou les cartes ; ceux-ci sont définis avec la syntaxe donnée ci-dessous.)
De plus, un certain nombre de types sont définis dans le prélude qui sont associés à des balises CBOR, tels que "tdate", "bigint", "regexp", etc.
3.4. Tableaux (Arrays)
Les définitions de tableaux entourent un groupe avec des crochets.
Pour chaque entrée, un indicateur d'occurrence tel que spécifié dans la Section 3.2 est autorisé.
Par exemple :
unlimited-people = [* person]
one-or-two-people = [1*2 person]
at-least-two-people = [2* person]
person = (
name: tstr,
age: uint,
)
Le groupe "person" est défini de telle manière que le répéter dans le tableau génère à chaque fois des noms et des âges alternés, donc ce sont quatre valeurs valides pour une donnée de type "unlimited-people" :
["roundlet", 1047, "psychurgy", 2204, "extrarhythmical", 2231]
[]
["aluminize", 212, "climograph", 4124]
["penintime", 1513, "endocarditis", 4084, "impermeator", 1669,
"coextension", 865]
3.5. Cartes (Maps)
La syntaxe pour spécifier des cartes mérite une attention particulière, ainsi qu'un certain nombre d'optimisations et de commodités, car elle est susceptible d'être le point focal de nombreuses spécifications utilisant CDDL. Bien que la syntaxe ne distingue pas strictement l'utilisation de structure (struct) et de table des cartes, elle répond spécifiquement à chacune d'elles.
Mais d'abord, réitérons une fonctionnalité de CBOR qu'il a héritée de JSON : les paires clé/valeur dans les cartes CBOR n'ont pas d'ordre fixe. (On pourrait imaginer des situations où fixer l'ordre pourrait être utile. Par exemple, un décodeur pourrait rechercher des valeurs liées aux clés entières 1, 3 et 7. Si l'ordre était fixe et que le décodeur rencontre la clé 4 sans avoir rencontré la clé 3, il pourrait conclure que la clé 3 n'est pas disponible sans faire de comptabilité plus compliquée. Malheureusement, ni JSON ni CBOR ne supportent cela, donc aucune tentative n'a été faite pour supporter cela dans CDDL non plus.)
3.5.1. Structures (Structs)
L'utilisation "struct" des cartes est similaire à la façon dont les objets JSON sont utilisés dans de nombreuses applications JSON.
Une carte est définie de la même manière que celle pour définir un tableau (voir Section 3.4), sauf pour l'utilisation d'accolades "{}" au lieu de crochets "[]".
Un indicateur d'occurrence tel que spécifié dans la Section 3.2 est autorisé pour chaque entrée de groupe.
Ce qui suit est un exemple d'un enregistrement avec une structure intégrée :
Geography = [
city : tstr,
gpsCoordinates : GpsCoordinates,
]
GpsCoordinates = {
longitude : uint, ; degrés, mis à l'échelle par 10^7
latitude : uint, ; degrés, mis à l'échelle par 10^7
}
Lors de l'encodage, l'enregistrement Geography est encodé en utilisant un tableau CBOR avec deux membres (les clés pour les entrées de groupe sont ignorées), tandis que la structure GpsCoordinates est encodée comme une carte CBOR avec deux paires clé/valeur.
Les types utilisés dans une structure peuvent être définis dans des règles séparées ou juste en place (potentiellement placés entre parenthèses, comme pour les choix). Par exemple :
located-samples = {
sample-point: int,
samples: [+ float],
}
où "located-samples" est le type de données à utiliser lors de la référence à la structure, et "sample-point" et "samples" sont les clés à utiliser. C'est en fait un exemple complet : un identifiant qui est suivi par deux points peut être directement utilisé comme la chaîne de texte pour une clé de membre (nous parlons d'une clé de membre "mot nu"), tout comme une chaîne entre guillemets doubles ou un nombre. (Lorsque d'autres types -- en particulier, des types qui contiennent plus d'une valeur -- sont utilisés comme types de clés, ils sont suivis d'une double flèche ; voir ci-dessous.)
Si une clé de chaîne de texte ne correspond pas à la syntaxe d'un identifiant (ou si le spécificateur préfère simplement utiliser des guillemets doubles), la syntaxe de chaîne de texte peut également être utilisée dans la position de la clé de membre, suivie de deux points. L'exemple ci-dessus aurait donc pu être écrit avec des chaînes citées dans les positions de clé de membre.
Plus généralement, les types spécifiés d'une manière autre que celles listées pour les cas décrits ci-dessus peuvent être utilisés dans une position de type de clé en les faisant suivre d'une double flèche -- en particulier, la double flèche est nécessaire si un type est nommé par un identifiant (qui, lorsqu'il est suivi de deux points, serait interprété comme un "mot nu" et transformé en chaîne de texte). Une chaîne de texte littérale donne également lieu à un type (qui contient une seule valeur seulement -- la chaîne donnée), donc une autre forme pour cet exemple est :
located-samples = {
"sample-point" => int,
"samples" => [+ float],
}
Voir la Section 3.5.4 ci-dessous pour savoir comment le raccourci deux-points (":") décrit ici ajoute également une sémantique implicite.
Une meilleure façon de démontrer l'utilisation de la double flèche peut être :
located-samples = {
sample-point: int,
samples: [+ float],
* equipment-type => equipment-tolerances,
}
equipment-type = [name: tstr, manufacturer: tstr]
equipment-tolerances = [+ [float, float]]
L'exemple ci-dessous définit une structure avec des entrées optionnelles : nom d'affichage (comme chaîne de texte), les composants du nom prénom et nom de famille (comme chaînes de texte), et des informations sur l'âge (comme entier non signé).
PersonalData = {
? displayName: tstr,
NameComponents,
? age: uint,
}
NameComponents = (
? firstName: tstr,
? familyName: tstr,
)
Notez que la définition de groupe pour NameComponents ne génère pas une autre carte ; au lieu de cela, les quatre clés sont directement dans la structure construite par PersonalData.
Dans cet exemple, toutes les paires clé/valeur sont optionnelles du point de vue de CDDL. Sans indicateur d'occurrence, une entrée est obligatoire.
Si l'ajout de plus d'entrées non spécifiées par la spécification actuelle est souhaité, on peut ajouter cette possibilité explicitement :
PersonalData = {
? displayName: tstr,
NameComponents,
? age: uint,
* tstr => any
}
NameComponents = (
? firstName: tstr,
? familyName: tstr,
)
Figure 7 : Données personnelles : Exemple d'extensibilité
L'outil CDDL décrit à l'Annexe F a généré ce qui suit comme une instance acceptable pour cette spécification :
{"familyName": "agust", "antiforeignism": "pretzel",
"springbuck": "illuminatingly", "exuviae": "ephemeris",
"kilometrage": "frogfish"}
(Voir la Section 3.9 pour une façon d'identifier explicitement un point d'extension.)
3.5.2. Tables
Une table peut être spécifiée en définissant une carte avec des entrées où le type de clé permet plus qu'une simple valeur unique ; par exemple :
square-roots = {* x => y}
x = int
y = float
Ici, la clé dans chaque paire clé/valeur a le type de données x (défini comme int), et la valeur a le type de données y (défini comme float).
Si la spécification n'a pas besoin de restreindre l'un de x ou y (c'est-à-dire que l'application est libre de choisir par entrée), il peut être remplacé par le nom prédéfini "any".
Comme autre exemple, ce qui suit pourrait être utilisé comme table de conversion convertissant d'un entier ou flottant en une chaîne :
tostring = {* mynumber => tstr}
mynumber = int / float
3.5.3. Ordre non déterministe
Alors que la façon dont les tableaux sont appariés est entièrement déterminée par le formalisme PEG (voir Annexe A), l'appariement est plus compliqué pour les cartes, car les cartes n'ont pas d'ordre inhérent. Pour chaque paire nom/valeur candidate que l'algorithme PEG essaierait, un membre correspondant est choisi dans toute la carte. Pour certaines expressions de groupe, plus d'un membre dans la carte peut correspondre. Le plus souvent, cela n'a pas d'importance, car l'expression de groupe tend à consommer toutes les correspondances :
labeled-values = {
? fritz: number,
* label => value
}
label = text
value = number
Ici, si un membre avec la clé "fritz" est présent, il sera choisi par la première entrée du groupe ; tous les membres texte/nombre restants seront choisis par la seconde entrée (et s'il reste quelque chose de non choisi, la carte ne correspond pas).
Cependant, il est possible de construire des expressions de groupe où ce qui est réellement choisi est indéterminé, mais compte :
do-not-do-this = {
int => int,
int => 6,
}
Lorsque cette expression est appariée contre "{3: 5, 4: 6}", la première entrée de groupe pourrait choisir le "3: 5", laissant "4: 6" pour l'appariement de la seconde. Ou elle pourrait choisir "4: 6", ne laissant rien pour la seconde entrée. Ce non-déterminisme pathologique est causé par la spécification de "plus général" avant "plus spécifique" et par le fait d'avoir une règle générale qui ne consomme qu'un sous-ensemble des paires clé/valeur de la carte qu'elle est capable de faire correspondre -- les deux tendent à ne pas se produire dans les spécifications réelles des cartes. Au moment de la rédaction, les outils CDDL ne peuvent pas détecter de tels cas automatiquement, et pour la version actuelle de la spécification CDDL, il est simplement demandé au rédacteur de spécification de ne pas écrire de spécifications pathologiquement non déterministes.
(Le lecteur avisé se souviendra de ce qui était appelé "modèles de contenu ambigus" dans le Standard Generalized Markup Language (SGML) et "modèles de contenu non déterministes" en XML. Ce problème est lié à celui décrit ici, mais le problème ici est spécifiquement causé par le manque d'ordre dans les cartes, chose avec laquelle les langages de schéma XML n'ont pas à composer. Notez que le motif "interleave" de RELAX NG gère le manque d'ordre explicitement du côté de la spécification, tandis que les instances en XML ont toujours un ordre déterminé.)
3.5.4. Coupures dans les cartes (Cuts)
L'idiome d'extensibilité discuté ci-dessus pour les structures a un problème :
extensible-map-example = {
? "optional-key" => int,
* tstr => any
}
Dans cet exemple, il y a une clé optionnelle "optional-key", qui, lorsqu'elle est présente, correspond à un entier. Il y a aussi un joker pour tout ajout futur.
Malheureusement, l'élément de donnée
{ "optional-key": "nonsense" }
correspond à cette spécification : alors que la première entrée du groupe ne correspond pas, la seconde (le joker) correspond. Cela peut très bien être souhaitable (par exemple, si une extension future doit être autorisée à étendre le type de "optional-key"), mais dans de nombreux cas, ce n'est pas le cas.
En prévision d'une fonctionnalité potentielle plus générale appelée "coupures", CDDL permet d'insérer une coupure "^" dans la définition de l'entrée de carte :
extensible-map-example = {
? "optional-key" ^ => int,
* tstr => any
}
Une coupure dans cette position signifie qu'une fois que la clé de membre correspond à la partie nom d'une entrée qui porte une coupure, d'autres correspondances potentielles pour la clé du membre qui se produisent dans des entrées ultérieures dans le groupe de la carte ne sont plus autorisées. En d'autres termes, lorsqu'une entrée de groupe choisirait une paire clé/valeur basée simplement sur une clé correspondante, elle "verrouille" le choix -- cette règle s'applique, indépendamment du fait que la valeur corresponde également, donc quand elle ne correspond pas, la carte entière échoue à correspondre. En résumé, l'exemple ci-dessus ne correspond plus à la spécification modifiée avec la coupure.
Puisque le désir pour ce type d'appariement exclusif est si fréquent, le raccourci ":" est en fait défini pour inclure la sémantique de coupure. Ainsi, l'exemple précédent (incluant la coupure) peut être écrit plus simplement comme :
extensible-map-example = {
? "optional-key": int,
* tstr => any
}
ou encore plus court, en utilisant un mot nu pour la clé :
extensible-map-example = {
? optional-key: int,
* tstr => any
}
3.6. Balises (Tags)
Un type peut utiliser une balise CBOR (type majeur 6) en utilisant la notation de type de représentation, donnant #6.nnn(type) où nnn est un entier non signé donnant le numéro de balise et "type" est le type de l'élément de donnée balisé.
Par exemple, la ligne suivante du prélude CDDL (Annexe D) définit "biguint" comme un nom de type pour un grand nombre non signé N :
biguint = #6.2(bstr)
Les balises définies par [RFC7049] sont incluses dans le prélude. Les balises supplémentaires enregistrées depuis que [RFC7049] a été écrit doivent être ajoutées à une spécification CDDL selon les besoins ; par exemple, une balise d'identifiant unique universel (UUID) binaire pourrait être référencée comme "buuid" dans une spécification après avoir défini
buuid = #6.37(bstr)
Dans l'exemple suivant, l'utilisation de la balise 32 pour les URI est optionnelle :
my_uri = #6.32(tstr) / tstr
3.7. Déballage (Unwrapping)
Le groupe qui est utilisé pour définir une carte ou un tableau peut souvent être réutilisé dans la définition d'une autre carte ou tableau. De même, un type défini comme une balise porte un élément de donnée interne auquel on voudrait faire référence. Dans ces cas, il est expédient d'utiliser simplement le nom du type de carte, tableau ou balise comme une poignée pour le groupe ou le type défini à l'intérieur.
L'opérateur "déballer" (écrit en faisant précéder un nom par un caractère tilde "~") peut être utilisé pour dépouiller le type défini pour un nom d'une couche, exposant le groupe sous-jacent (pour les cartes et les tableaux) ou le type (pour les balises).
Par exemple, une application pourrait vouloir définir un en-tête de base et un en-tête avancé. Sans déballage, cela pourrait être fait comme suit :
basic-header-group = (
field1: int,
field2: text,
)
basic-header = [ basic-header-group ]
advanced-header = [
basic-header-group,
field3: bytes,
field4: number, ; comme dans le type balisé "time"
]
Le déballage simplifie cela en :
basic-header = [
field1: int,
field2: text,
]
advanced-header = [
~basic-header,
field3: bytes,
field4: ~time,
]
(Notez que l'omission du premier opérateur de déballage dans le dernier exemple conduirait à imbriquer le basic-header dans son propre tableau à l'intérieur de l'advanced-header, tandis que, avec le basic-header déballé, la définition du groupe à l'intérieur de basic-header est essentiellement répétée à l'intérieur de advanced-header, conduisant à un seul tableau. Cela peut être utilisé pour diverses applications souvent résolues par héritage dans les langages de programmation. L'effet du déballage peut également être décrit comme "enfilant" le groupe ou le type à l'intérieur du type référencé, ce qui a suggéré le caractère "~" ressemblant à un fil.)
3.8. Contrôles (Controls)
Un contrôle permet de relier un type cible avec un type contrôleur via un opérateur de contrôle.
La syntaxe pour un type de contrôle est "target .control-operator controller", où les opérateurs de contrôle sont des identifiants spéciaux préfixés par un point. (Notez que target ou controller pourrait avoir besoin d'être entre parenthèses.)
Un certain nombre d'opérateurs de contrôle sont définis à ce stade. D'autres opérateurs de contrôle peuvent être définis par de nouvelles versions de cette spécification ou en les enregistrant selon les procédures de la Section 6.1.
3.8.1. Opérateur de contrôle .size
Un contrôle ".size" contrôle la taille de la cible en octets par le type de contrôle. Le contrôle est défini pour les chaînes de texte et d'octets, où il contrôle directement le nombre d'octets dans la chaîne. Il est également défini pour les entiers non signés (voir ci-dessous). La Figure 8 montre un exemple d'utilisation pour les chaînes d'octets.
full-address = [[+ label], ip4, ip6]
ip4 = bstr .size 4
ip6 = bstr .size 16
label = bstr .size (1..63)
Figure 8 : Contrôle de la taille en octets
Lorsqu'il est appliqué à un entier non signé, le contrôle ".size" restreint la plage de cet entier en donnant un nombre maximum d'octets qui devraient être nécessaires dans une représentation informatique de cet entier non signé. En d'autres termes, "uint .size N" est équivalent à "0...BYTES_N", où BYTES_N == 256**N.
audio_sample = uint .size 3 ; 24 bits, équivalent à 0...16777216
Figure 9 : Contrôle de la taille entière en octets
Notez que, comme pour les restrictions de valeur dans CDDL, ce contrôle n'est pas une contrainte de représentation ; un nombre qui tient dans moins d'octets peut toujours être représenté sous cette forme, et une implémentation inefficace pourrait utiliser une forme plus longue (à moins que cela ne soit restreint par certaines contraintes de format en dehors de CDDL, telles que les règles de la Section 3.9 de [RFC7049]).
3.8.2. Opérateur de contrôle .bits
Un contrôle ".bits" sur une chaîne d'octets indique que, dans la cible, seuls les bits numérotés par un nombre dans le type de contrôle sont autorisés à être définis. (Les bits sont comptés de la manière habituelle, le bit numéro "n" étant défini dans "str" signifiant que "(str[n >> 3] & (1 << (n & 7))) != 0".) De même, un contrôle ".bits" sur un entier non signé "i" indique que pour tous les entiers non signés "n" où "(i & (1 << n)) != 0", "n" doit être dans le type de contrôle.
tcpflagbytes = bstr .bits flags
flags = &(
fin: 8,
syn: 9,
rst: 10,
psh: 11,
ack: 12,
urg: 13,
ece: 14,
cwr: 15,
ns: 0,
) / (4..7) ; bits de décalage de données
rwxbits = uint .bits rwx
rwx = &(r: 2, w: 1, x: 0)
Figure 10 : Contrôle des bits pouvant être définis
L'outil CDDL décrit à l'Annexe F génère les dix exemples d'instances suivants pour "tcpflagbytes" :
h'906d' h'01fc' h'8145' h'01b7' h'013d' h'409f' h'018e' h'c05f'
h'01fa' h'01fe'
Ces exemples n'illustrent pas que la spécification CDDL ci-dessus ne spécifie pas explicitement une taille de deux octets : une instance valide "tout effacé" des octets de drapeaux pourrait être "h''" ou "h'00'" ou même "h'000000'" également.
3.8.3. Opérateur de contrôle .regexp
Un contrôle ".regexp" indique que la chaîne de texte donnée comme cible doit correspondre à l'expression régulière XML Schema Definition (XSD) donnée comme valeur dans le type de contrôle. Les expressions régulières XSD sont définies à l'Annexe F de [W3C.REC-xmlschema-2-20041028].
nai = tstr .regexp "[A-Za-z0-9]+@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)+"
Figure 11 : Contrôle avec une regexp XSD
Un exemple correspondant à cette expression régulière :
3.8.3.1. Considérations d'utilisation
Notez que les expressions régulières XSD ne supportent pas les échappements \x ou \u habituels pour l'expression hexadécimale des octets ou des points de code Unicode. Cependant, dans CDDL, les expressions régulières XSD sont contenues dans des chaînes de texte, dont la notation littérale fournit des échappements \u ; cela devrait suffire pour la plupart des applications qui utilisent des expressions régulières pour les chaînes de texte. (Notez que cela signifie également qu'il y a un niveau d'échappement de chaîne avant que les règles d'échappement XSD ne soient appliquées.)
Les expressions régulières XSD supportent la soustraction de classe de caractères, une fonctionnalité souvent absente des bibliothèques d'expressions régulières ; les rédacteurs de spécifications peuvent vouloir utiliser cette fonctionnalité avec parcimonie. Des considérations similaires s'appliquent aux classes de caractères Unicode ; là où elles sont utilisées, la spécification qui emploie CDDL DEVRAIT identifier quelles versions Unicode sont adressées.
D'autres surprises pour les utilisateurs peu fréquents des expressions régulières XSD peuvent inclure ce qui suit :
-
Pas de support direct pour l'insensibilité à la casse. Bien que l'insensibilité à la casse soit passée de mode dans la conception de protocoles, elle est parfois nécessaire et doit alors être exprimée manuellement comme dans "[Cc][Aa][Ss][Ee]".
-
Le support des classes de caractères populaires telles que \w et \d est basé sur les propriétés de caractères Unicode ; ce n'est souvent pas ce qui est souhaité dans un protocole basé sur ASCII et peut donc conduire à des surprises. (\s et \S ont leurs significations plus conventionnelles, et "." correspond à tout caractère sauf les caractères de fin de ligne \r ou \n.)
3.8.3.2. Discussion
Il existe de nombreuses variantes d'expressions régulières utilisées dans la communauté de la programmation. Par exemple, les Perl-Compatible Regular Expressions (PCRE) sont largement utilisées et sont probablement plus utiles que les expressions régulières XSD. Cependant, il n'y a pas de référence normative pour les PCRE qui pourrait être utilisée dans le présent document. Au lieu de cela, nous optons pour les expressions régulières XSD pour le moment. Il existe un précédent pour ce choix à l'IETF, par exemple, dans YANG [RFC7950].
Notez que CDDL utilise les contrôles comme son point d'extension principal. Cela crée l'opportunité d'ajouter d'autres formats d'expressions régulières en plus de celui référencé ici, si désiré. À titre d'exemple, une proposition pour un contrôle ".pcre" est définie dans [CDDL-Freezer].
3.8.4. Opérateurs de contrôle .cbor et .cborseq
Un contrôle ".cbor" sur une chaîne d'octets indique que la chaîne d'octets porte un élément de donnée encodé en CBOR. Décodé, l'élément de donnée correspond au type donné comme argument de droite (type1 dans l'exemple suivant).
bytes .cbor type1
De même, un contrôle ".cborseq" sur une chaîne d'octets indique que la chaîne d'octets porte une séquence d'éléments de données encodés en CBOR. Lorsque les éléments de données sont pris comme un tableau, le tableau correspond au type donné comme argument de droite (type2 dans l'exemple suivant).
bytes .cborseq type2
(La conversion de la séquence encodée en un tableau peut être effectuée, par exemple, en enveloppant la chaîne d'octets entre les deux octets 0x9f et 0xff et en décodant la chaîne d'octets enveloppée comme un élément de donnée encodé en CBOR.)
3.8.5. Opérateurs de contrôle .within et .and
Un contrôle ".and" sur un type indique que l'élément de donnée correspond à la fois au type de gauche et au type donné à droite. (Formellement, le type résultant est l'intersection des deux types donnés.)
type1 .and type2
Une variante du contrôle ".and" est le contrôle ".within", qui exprime une intention supplémentaire : le type de gauche est censé être un sous-ensemble du type de droite.
type1 .within type2
Alors que les deux formes ont la sémantique formelle identique (intersection), l'intention de la forme ".within" est que le côté droit donne des conseils aux types autorisés sur le côté gauche, qui est généralement une prise (Section 3.9) :
message = $message .within message-structure
message-structure = [message_type, *message_option]
message_type = 0..255
message_option = any
$message /= [3, dough: text, topping: [* text]]
$message /= [4, noodles: text, sauce: text, parmesan: bool]
Pour ".within", un outil pourrait signaler une erreur si type1 autorise des éléments de données qui ne sont pas autorisés par type2. En revanche, pour ".and", il n'y a pas d'attente que type1 soit déjà un sous-ensemble de type2.
3.8.6. Opérateurs de contrôle .lt, .le, .gt, .ge, .eq, .ne, et .default
Les contrôles .lt, .le, .gt, .ge, .eq et .ne spécifient une contrainte sur le type de gauche pour être une valeur inférieure à, inférieure ou égale à, supérieure à, supérieure ou égale à, égale à, ou non égale à une valeur donnée comme un type de droite (contenant juste cette valeur unique). Dans la présente spécification, les quatre premiers contrôles (.lt, .le, .gt et .ge) sont définis uniquement pour les types numériques, car ceux-ci ont une relation d'ordre naturelle.
speed = number .ge 0 ; unité : m/s
.ne et .eq sont définis à la fois pour les valeurs numériques et les valeurs d'autres types. Si l'une des valeurs n'est pas d'un type numérique, l'égalité est déterminée comme suit : les chaînes de texte sont égales (satisfont .eq / ne satisfont pas .ne) s'elles sont identiques octet par octet ; la même chose s'applique pour les chaînes d'octets. Les tableaux sont égaux s'ils ont le même nombre d'éléments, tous égaux par paire dans l'ordre entre les tableaux. Les cartes sont égales si elles ont le même nombre de paires clé/valeur, et il y a égalité par paire entre les paires clé/valeur entre les deux cartes. Les valeurs balisées sont égales si elles ont toutes deux la même balise et que les valeurs sont égales. Les valeurs des types simples correspondent si elles sont les mêmes valeurs. Les types numériques qui se produisent dans des tableaux, des cartes ou des valeurs balisées sont égaux si leur valeur numérique est égale et qu'ils sont tous deux entiers ou tous deux valeurs à virgule flottante. Tous les autres cas ne sont pas égaux (par exemple, comparer une chaîne de texte avec une chaîne d'octets).
Une variante du contrôle ".ne" est le contrôle ".default", qui exprime une intention supplémentaire : la valeur spécifiée par le type de droite est destinée à être une valeur par défaut pour le type de gauche donné, et le contrôle .ne implicite est là pour empêcher cette valeur d'être envoyée sur le fil. Ce contrôle n'est significatif que lorsque le type de contrôle est utilisé dans un contexte optionnel ; sinon, il n'y aurait aucun moyen d'utiliser la valeur par défaut.
timer = {
time: uint,
? displayed-step: (number .gt 0) .default 1
}
3.9. Prise/Fiche (Socket/Plug)
Pour les choix de type et les choix de groupe, un mécanisme est défini qui facilite le début avec des choix vides et leur assemblage ultérieur, potentiellement dans des fichiers séparés qui sont concaténés pour construire la spécification complète.
Par convention, les points d'extension CDDL sont marqués avec un signe dollar initial (types) ou deux signes dollar initiaux (groupes). Les outils honorent cette convention en ne levant pas d'erreur si un tel type ou groupe n'est pas défini du tout ; le symbole est alors pris comme un choix de type vide (choix de groupe), c'est-à-dire qu'aucun choix n'est disponible.
tcp-header = {seq: uint, ack: uint, * $$tcp-option}
; plus tard, dans un fichier différent
$$tcp-option //= (
sack: [+(left: uint, right: uint)]
)
; et, peut-être dans un autre fichier
$$tcp-option //= (
sack-permitted: true
)
Les noms qui commencent par un simple "$" sont des "prises de type" (type sockets), commençant comme un type vide, et destinés à être étendus via "/=". Les noms qui commencent par un double "$$" sont des "prises de groupe" (group sockets), commençant comme un choix de groupe vide, et destinés à être étendus via "//=". Dans les deux cas, ce n'est pas une erreur s'il n'y a pas de définition pour une prise du tout ; cela signifie alors qu'il n'y a aucun moyen de satisfaire la règle (c'est-à-dire que le choix est vide).
Par convention, toutes les définitions (fiches) pour les noms de prise doivent être des augmentations, c'est-à-dire qu'elles doivent utiliser "/=" et "//=", respectivement.
Pour reprendre l'exemple illustré à la Figure 7, le mécanisme prise/fiche pourrait être utilisé comme indiqué à la Figure 12 :
PersonalData = {
? displayName: tstr,
NameComponents,
? age: uint,
* $$personaldata-extensions
}
NameComponents = (
? firstName: tstr,
? familyName: tstr,
)
; Ce qui précède fonctionne déjà tel quel.
; Mais ensuite, nous pouvons ajouter plus tard :
$$personaldata-extensions //= (
favorite-salsa: tstr,
)
; et encore, ailleurs :
$$personaldata-extensions //= (
shoesize: uint,
)
Figure 12 : Exemple de données personnelles : Utilisation de l'extensibilité Prise/Fiche
3.10. Génériques (Generics)
En utilisant des chevrons, le côté gauche d'une règle peut ajouter des paramètres formels après le nom défini, comme dans :
messages = message<"reboot", "now"> / message<"sleep", 1..100>
message<t, v> = {type: t, value: v}
Lors de l'utilisation d'une règle générique, les paramètres formels sont liés aux arguments réels fournis (utilisant également des chevrons), dans la portée de la règle générique (comme s'il y avait une règle de la forme paramètre = argument).
Les règles génériques peuvent être utilisées pour établir des noms pour les types et les groupes.
(À l'heure actuelle, il existe certaines limitations à l'imbrication des génériques dans l'outil CDDL décrit à l'Annexe F.)
3.11. Priorité des opérateurs
Comme avec tout langage qui a plusieurs caractéristiques syntaxiques telles que des opérateurs préfixes et infixes, CDDL a des opérateurs qui se lient plus étroitement que d'autres. Cela devient plus compliqué que, disons, en ABNF, car CDDL a à la fois des types et des groupes, avec des opérateurs qui sont spécifiques à ces concepts. Les opérateurs de type (tels que "/" pour le choix de type) opèrent sur des types, tandis que les opérateurs de groupe (tels que "//" pour le choix de groupe) opèrent sur des groupes. Les types peuvent simplement être utilisés dans des groupes, mais les groupes doivent être mis entre crochets (comme tableaux ou cartes) pour devenir des types. Ainsi, les opérateurs de type se lient naturellement plus étroitement que les opérateurs de groupe.
Par exemple, dans
t = [group1]
group1 = (a / b // c / d)
a = 1 b = 2 c = 3 d = 4
group1 est un choix de groupe entre le choix de type de a et b et le choix de type de c et d. Cela devient plus pertinent une fois que les clés de membre et/ou les occurrences sont ajoutées :
t = {group2}
group2 = (? ab: a / b // cd: c / d)
a = 1 b = 2 c = 3 d = 4
est un choix de groupe entre le membre optionnel "ab" de type a ou b et le membre "cd" de type c ou d. Notez que l'optionnalité est attachée au premier choix ("ab"), pas au second choix.
De même, dans
t = [group3]
group3 = (+ a / b / c)
a = 1 b = 2 c = 3
group3 est une répétition d'un choix de type entre a, b, et c ; si seul a doit être répétable, un choix de groupe est nécessaire pour concentrer l'occurrence :
t = [group4]
group4 = (+ a // b / c)
a = 1 b = 2 c = 3
group4 est un choix de groupe entre un a répétable et un seul b ou c.
Un commentaire a été que la sémantique de group3 pourrait être contre-intuitive. En général, comme avec de nombreux autres langages avec des règles de priorité d'opérateur, il est encouragé au rédacteur de spécification de ne pas compter sur elles, mais d'insérer des parenthèses libéralement pour guider les lecteurs qui ne sont pas familiers avec les règles de priorité CDDL :
t = [group4a]
group4a = ((+ a) // (b / c))
a = 1 b = 2 c = 3
Les priorités des opérateurs, en séquence de liaison lâche à serrée, sont définies à l'Annexe B et résumées dans le Tableau 1. (Les arités données sont 1 pour les opérateurs préfixes unaires et 2 pour les opérateurs infixes binaires.)
| Opérateur | Arité | Opère sur | Priorité |
|---|---|---|---|
| = | 2 | nom = type, nom = groupe | 1 |
| /= | 2 | nom /= type | 1 |
| //= | 2 | nom //= groupe | 1 |
| // | 2 | groupe // groupe | 2 |
| , | 2 | groupe, groupe | 3 |
| * | 1 | * groupe | 4 |
| n*m | 1 | n*m groupe | 4 |
| + | 1 | + groupe | 4 |
| ? | 1 | ? groupe | 4 |
| => | 2 | type => type | 5 |
| : | 2 | nom: type | 5 |
| / | 2 | type / type | 6 |
| .. | 2 | type..type | 7 |
| ... | 2 | type...type | 7 |
| .ctrl | 2 | type .ctrl type | 7 |
| & | 1 | &groupe | 8 |
| ~ | 1 | ~type | 8 |
Tableau 1 : Résumé des priorités des opérateurs