Aller au contenu principal

2. Le style de spécification de structure de données

CDDL se concentre sur les styles de spécification utilisés dans la communauté employant le modèle de données tel que lancé par JSON et maintenant affiné dans CBOR.

Il existe un certain nombre d'éléments plus ou moins atomiques d'un modèle de données CBOR, tels que les nombres, les valeurs simples (faux, vrai, nil), les chaînes de texte et les chaînes d'octets ; CDDL ne se concentre pas sur la spécification de leur structure. CDDL permet bien sûr également d'ajouter une balise CBOR à un élément de donnée.

Au-delà de ces éléments atomiques, d'autres composants d'une langue de définition de structure de données sont les types de données utilisés pour la composition : tableaux et cartes dans CBOR (appelés "tableaux" et "objets" dans JSON). Bien qu'il ne s'agisse que de deux formats de représentation, ils sont utilisés pour spécifier quatre styles de composition vaguement distinguables :

  • Un vecteur (Vector) : un tableau d'éléments qui ont pour la plupart la même sémantique. L'ensemble des signatures associées à un élément de donnée signé est une application typique d'un vecteur.

  • Un enregistrement (Record) : un tableau dont les éléments ont une sémantique définie par la position différente, comme détaillé dans la définition de la structure de données. Un point 2D, spécifié comme un tableau d'une coordonnée x (qui vient en premier) et d'une coordonnée y (venant en second), est un exemple d'enregistrement, tout comme la paire exposant (premier) et mantisse (second) dans une fraction décimale CBOR.

  • Une table (Table) : une carte d'un domaine de clés de carte vers un domaine de valeurs de carte, qui ont pour la plupart la même sémantique. Un ensemble de balises de langue, chacune mappée à une chaîne de texte traduite dans cette langue spécifique, est un exemple de table. Le domaine de clé n'est généralement pas limité à un ensemble spécifique par la spécification mais est ouvert pour l'application, par exemple, dans une table mappant des adresses IP à des adresses MAC (Media Access Control), la spécification ne tente pas de prévoir toutes les adresses IP possibles. Dans un langage tel que JavaScript, une "Map" (par opposition à un simple "Object") serait souvent utilisée pour atteindre la généralité du domaine de clé.

  • Une structure (Struct) : une carte d'un domaine de clés de carte tel que défini par la spécification vers un domaine de valeurs de carte dont la sémantique de chacune est liée à une clé de carte spécifique. C'est ce que beaucoup de gens ont à l'esprit lorsqu'ils pensent aux objets JSON ; CBOR ajoute la capacité d'utiliser des clés de carte qui ne sont pas seulement des chaînes de texte. Les structures peuvent être utilisées pour résoudre des problèmes similaires à ceux pour lesquels les enregistrements sont utilisés ; l'utilisation de clés de carte explicites facilite l'optionnalité et l'extensibilité.

Deux concepts importants fournissent la base de CDDL :

  1. Au lieu de définir les quatre types de composition dans CDDL séparément, ou même de définir un type pour les tableaux (vecteurs et enregistrements) et un type pour les cartes (tables et structures), il n'y a qu'un seul type de composition dans CDDL : le groupe (Section 2.1).

  2. L'autre concept important est celui d'un type. La spécification CDDL entière définit un type (celui défini par sa première règle), qui est formellement l'ensemble des éléments de données CBOR qui sont acceptables comme "instances" pour cette spécification. CDDL prédéfinit un certain nombre de types de base tels que "uint" (entier non signé) ou "tstr" (chaîne de texte), utilisant souvent une notation formelle simple pour les éléments de données CBOR. Chaque valeur qui peut être exprimée comme un élément de donnée CBOR est également un type à part entière, par exemple, "1". Un type peut être construit comme un choix d'autres types, par exemple, un "int" est soit un "uint" soit un "nint" (entier négatif). Enfin, un type peut être construit comme un tableau ou une carte à partir d'un groupe.

Le reste de cette section introduit un certain nombre de concepts de base de CDDL, et la Section 3 définit une syntaxe supplémentaire. L'Annexe C donne un résumé concis de la sémantique de CDDL.

2.1. Groupes et Composition dans CDDL

Les groupes CDDL sont des listes d'entrées de groupe, dont chacune peut être une paire nom/valeur ou une expression de groupe plus complexe (qui représente alors à son tour une séquence de paires nom/valeur). Un groupe CDDL est une production dans une grammaire qui correspond à certaines séquences de paires nom/valeur mais pas à d'autres. La grammaire est basée sur les concepts des Parsing Expression Grammars (PEGs) (voir Annexe A).

Dans un contexte de tableau, seule la valeur de la paire nom/valeur est représentée ; le nom est une annotation seulement (et peut être omis de la spécification du groupe s'il n'est pas nécessaire). Dans un contexte de carte, les noms deviennent les clés de carte ("clés de membre").

Dans un contexte de tableau, la séquence réelle des éléments dans le groupe est importante, car cette séquence est l'information qui permet d'associer les éléments réels du tableau aux entrées dans le groupe. Dans un contexte de carte, la séquence des entrées dans un groupe n'est pas pertinente (mais il est toujours nécessaire d'écrire les entrées de groupe dans une séquence).

Un tableau correspond à une spécification donnée comme un groupe lorsque le groupe correspond à une séquence de paires nom/valeur dont les parties valeur correspondent exactement aux éléments du tableau dans l'ordre.

Une carte correspond à une spécification donnée comme un groupe lorsque le groupe correspond à une séquence de paires nom/valeur de sorte que toutes ces paires nom/valeur sont présentes dans la carte et que la carte n'a aucune paire nom/valeur qui n'est pas couverte par le groupe.

Un exemple simple d'utilisation directe d'un groupe dans une définition de carte est :

person = {
age: int,
name: tstr,
employer: tstr,
}

Figure 1 : Utilisation directe d'un groupe dans une carte

Les trois entrées du groupe sont écrites entre les accolades qui créent la carte : ici, "age", "name" et "employer" sont les noms qui se transforment en chaînes de texte de clé de carte, et "int" et "tstr" (chaîne de texte) sont les types des valeurs de carte sous ces clés.

Un groupe en lui-même (sans créer de carte autour) peut être placé entre parenthèses (rondes) et recevoir un nom en l'utilisant dans une règle :

pii = (
age: int,
name: tstr,
employer: tstr,
)

Figure 2 : Un groupe de base

Cette définition de groupe séparée et nommée nous permet de reformuler la Figure 1 comme :

person = {
pii
}

Figure 3 : Utilisation d'un groupe par nom

Notez que les accolades (courbes) signifient la création d'une carte ; les groupes eux-mêmes sont neutres quant à savoir s'ils seront utilisés dans une carte ou un tableau.

Comme le montre la Figure 1, les parenthèses pour les groupes sont optionnelles lorsqu'il y a un autre ensemble de crochets présent. Notez qu'elles peuvent toujours être utilisées, conduisant à cet exemple pas très réaliste, mais parfaitement valide :

person = {(
age: int,
name: tstr,
employer: tstr,
)}

Figure 4 : Utilisation d'un groupe entre parenthèses dans une carte

Les groupes peuvent être utilisés pour factoriser des parties communes de structures, par exemple, au lieu d'écrire des spécifications en style copier/coller, comme dans la Figure 5, on peut factoriser le sous-groupe commun, choisir un nom pour celui-ci et n'écrire que les parties spécifiques dans les cartes individuelles (Figure 6).

person = {
age: int,
name: tstr,
employer: tstr,
}

dog = {
age: int,
name: tstr,
leash-length: float,
}

Figure 5 : Cartes avec Copier/Coller

person = {
identity,
employer: tstr,
}

dog = {
identity,
leash-length: float,
}

identity = (
age: int,
name: tstr,
)

Figure 6 : Utilisation d'un groupe pour la factorisation

Notez que les listes à l'intérieur des accolades dans les définitions ci-dessus constituent des groupes (anonymes), tandis que "identity" est un groupe nommé, qui peut ensuite être inclus comme partie d'autres groupes (anonymes comme dans l'exemple, ou eux-mêmes nommés).

2.1.1. Utilisation

Les groupes sont l'instrument utilisé pour composer des structures de données avec CDDL. C'est une question de style dans la définition de ces structures que de définir des groupes (anonymement) directement dans leurs contextes ou de les définir dans une règle séparée et de les référencer avec leur nom respectif (éventuellement plus d'une fois).

Avec cela, on est autorisé à définir toutes les petites parties de ses structures de données et à composer de plus grandes unités de données de protocole avec celles-ci ou à n'avoir qu'une seule grande unité de données de protocole qui a toutes les définitions ad hoc là où elles sont nécessaires.

2.1.2. Syntaxe

La syntaxe de composition est destinée à être concise et facile à lire :

  • Le début et la fin d'un groupe peuvent être marqués par "(" et ")".

  • Les définitions d'entrées à l'intérieur d'un groupe sont notées comme suit : keytype => valuetype, (lire "keytype mappe vers valuetype"). La virgule est en fait optionnelle (pas seulement dans l'entrée finale), mais il est considéré comme un bon style de la mettre. La double flèche peut être remplacée par deux points dans le cas courant d'utilisation directe d'une chaîne de texte ou d'un littéral entier comme clé ; voir Section 3.5.1. C'est aussi la manière courante de nommer les éléments d'un tableau juste pour la documentation ; voir Section 3.4.

Une entrée de base consiste en un keytype et un valuetype, tous deux étant des types (Section 2.2) ; cette entrée correspond à toute paire nom/valeur dont le nom est dans le keytype et la valeur est dans le valuetype.

Un groupe défini comme une séquence d'entrées de groupe correspond à toute séquence de paires nom/valeur qui est composée par concaténation dans l'ordre de ce à quoi les entrées correspondent.

Une définition de groupe peut également contenir des choix entre des groupes ; voir Section 2.2.2.

2.2. Types

2.2.1. Valeurs

Des valeurs telles que des nombres et des chaînes peuvent être utilisées à la place d'un type. (Par exemple, c'est une chose très courante à faire pour un type de clé, assez courante pour que CDDL fournisse une syntaxe de commodité supplémentaire pour cela.)

La notation de valeur est basée sur le langage C, mais n'offre pas toutes les variations syntaxiques (voir Annexe B pour les détails). La notation de valeur pour les nombres hérite de C la distinction entre les valeurs entières (pas de partie fractionnaire ou d'exposant donné -- NR1 [ISO6093] ; "NR" signifie "numerical representation" (représentation numérique)) et les valeurs à virgule flottante (où une partie fractionnaire, un exposant ou les deux sont présents -- NR2 ou NR3), donc le type "1" n'inclut aucun nombre à virgule flottante tandis que les types "1e3" et "1.5" sont tous deux des nombres à virgule flottante et n'incluent aucun nombre entier.

2.2.2. Choix (Choices)

De nombreux endroits qui autorisent un type autorisent également un choix entre des types, délimité par un "/" (barre oblique). La construction de choix entière peut être mise entre parenthèses si cela est nécessaire pour rendre la construction non ambiguë (veuillez voir l'Annexe B pour les détails de la grammaire CDDL).

Les choix de valeurs peuvent être utilisés pour exprimer des énumérations :

attire = "bow tie" / "necktie" / "Internet attire"
protocol = 6 / 17

De manière analogue aux types, CDDL autorise également des choix entre des groupes, délimités par un "//" (double barre oblique). Notez que l'opérateur "//" se lie beaucoup plus faiblement que les autres opérateurs CDDL, donc chaque ligne à l'intérieur de "delivery" dans l'exemple suivant est sa propre alternative dans le choix de groupe :

address = { delivery }

delivery = (
street: tstr, ? number: uint, city //
po-box: uint, city //
per-pickup: true
)

city = (
name: tstr, zip-code: uint
)

Un choix de groupe correspond à l'union des ensembles de séquences de paires nom/valeur que les alternatives dans le choix peuvent.

Pour les choix de types et les choix de groupes, des alternatives supplémentaires peuvent être ajoutées à une règle plus tard dans des règles séparées en utilisant "/=" et "//=", respectivement, au lieu de "=" :

attire /= "swimwear"

delivery //= (
lat: float, long: float, drone-type: tstr
)

Ce n'est pas une erreur si un nom est utilisé pour la première fois avec un "/=" ou "//=" (il n'est pas nécessaire de le "créer" avec "=").

2.2.2.1. Plages (Ranges)

Au lieu de nommer toutes les valeurs qui composent un choix, CDDL permet de construire une plage à partir de deux valeurs qui sont dans une relation d'ordre : une borne inférieure (première valeur) et une borne supérieure (seconde valeur). Une plage peut inclure les deux bornes données (notée en joignant deux valeurs par ".."), ou elle peut inclure la borne inférieure et exclure la borne supérieure (notée en utilisant plutôt "..."). Si la borne inférieure dépasse la borne supérieure, le type résultant est l'ensemble vide (ce comportement peut être souhaitable lorsque des génériques (Section 3.10) sont utilisés).

device-address = byte
max-byte = 255
byte = 0..max-byte ; plage inclusive
first-non-byte = 256
byte1 = 0...first-non-byte ; byte1 est équivalent à byte

CDDL n'autorise actuellement que des plages entre des entiers (correspondant à des valeurs entières) ou entre des valeurs à virgule flottante (correspondant à des valeurs à virgule flottante). Si les deux sont nécessaires dans un type, un choix de type entre les deux sortes de plages peut être (maladroitement) utilisé :

int-range = 0..10 ; seuls les entiers correspondent
float-range = 0.0..10.0 ; seuls les flottants correspondent
BAD-range1 = 0..10.0 ; NON DÉFINI
BAD-range2 = 0.0..10 ; NON DÉFINI
numeric-range = int-range / float-range

(Voir aussi les opérateurs de contrôle .lt/.ge et .le/.gt dans la Section 3.8.6.)

Notez que le point est un caractère de continuation de nom valide dans CDDL, donc

min..max

n'est pas une expression de plage mais un seul nom. Lors de l'utilisation d'un nom comme côté gauche d'un opérateur de plage, utilisez un espacement comme dans

min .. max

pour séparer l'opérateur de plage.

2.2.2.2. Transformer un groupe en un choix

Certains choix sont construits à partir de grands nombres de valeurs, souvent des entiers, dont chacun reçoit de préférence un nom sémantique dans la spécification. Au lieu de nommer chacun de ces entiers et de les accumuler ensuite dans un choix, CDDL permet de construire un choix à partir d'un groupe en le préfixant avec un caractère "&" :

terminal-color = &basecolors
basecolors = (
black: 0, red: 1, green: 2, yellow: 3,
blue: 4, magenta: 5, cyan: 6, white: 7,
)
extended-color = &(
basecolors,
orange: 8, pink: 9, purple: 10, brown: 11,
)

Comme pour l'utilisation de groupes dans les tableaux (Section 3.4), les noms de membres n'ont qu'une valeur documentaire (en particulier, ils pourraient être utilisés par un outil lors de l'affichage d'entiers qui sont pris dans ce choix).

2.2.3. Types de représentation

CDDL permet la spécification d'un type d'élément de donnée en se référant à la représentation CBOR (spécifiquement, aux types majeurs et aux informations supplémentaires ; voir Section 2 de [RFC7049]). Comment cela est utilisé devrait être évident d'après le prélude (Annexe D) : un dièse ("#") suivi optionnellement d'un nombre de 0 à 7 identifiant le type majeur, qui peut ensuite être suivi d'un point et d'un nombre spécifiant l'information supplémentaire. Cette construction spécifie l'ensemble des valeurs qui peuvent être sérialisées en CBOR (c'est-à-dire "any"), par le type majeur donné si un est donné, ou par le type majeur donné avec l'information supplémentaire si les deux sont donnés. Lorsqu'un type majeur de 6 (Tag) est utilisé, le type de l'élément balisé peut être spécifié en l'ajoutant entre parenthèses.

Notez que bien que cette notation soit basée sur la sérialisation CBOR, il s'agit d'un ensemble de valeurs au niveau du modèle de données, par exemple, "#7.25" spécifie l'ensemble des valeurs qui peuvent être représentées comme des flottants de demi-précision ; cela n'oblige pas ces valeurs à être également sérialisées comme des flottants de demi-précision : CDDL ne fournit aucun moyen linguistique pour restreindre le choix des variantes de sérialisation. Cela permet également l'utilisation de CDDL avec JSON, qui utilise une manière fondamentalement différente de sérialiser (certaines des) mêmes valeurs.

Il peut être nécessaire de faire usage de types de représentation en dehors du prélude, par exemple, une spécification pourrait commencer par utiliser une balise existante d'une manière plus spécifique ou pourrait définir une nouvelle balise non définie dans le prélude :

my_breakfast = #6.55799(breakfast)   ; cbor-any est trop général !
breakfast = cereal / porridge
cereal = #6.998(tstr)
porridge = #6.999([liquid, solid])
liquid = milk / water
milk = 0
water = 1
solid = tstr

2.2.4. Type racine (Root Type)

Il n'y a pas de syntaxe spéciale pour identifier la racine d'une définition de structure de données CDDL : ce rôle est simplement pris par la première règle définie dans le fichier.

Ceci est motivé par l'approche descendante habituelle pour définir des structures de données, décomposant une grande unité de structure de données en parties plus petites ; cependant, sauf pour le type racine, il n'est pas nécessaire de suivre strictement cette séquence.

(Notez qu'il n'y a aucun moyen d'utiliser un groupe comme racine -- ce doit être un type.)