Passa al contenuto principale

2. Lo stile della specifica della struttura dati

CDDL si concentra sugli stili di specifica in uso nella comunità che impiega il modello dati come introdotto da JSON e ora perfezionato in CBOR.

Esistono un certo numero di elementi più o meno atomici di un modello dati CBOR, come numeri, valori semplici (false, true, nil), stringhe di testo e stringhe di byte; CDDL non si concentra sulla specifica della loro struttura. CDDL ovviamente consente anche di aggiungere un tag CBOR a un elemento dati.

Oltre a questi elementi atomici, ulteriori componenti di un linguaggio di definizione della struttura dati sono i tipi di dati utilizzati per la composizione: array e mappe in CBOR (chiamati "array" e "oggetti" in JSON). Sebbene questi siano solo due formati di rappresentazione, vengono utilizzati per specificare quattro stili di composizione vagamente distinguibili:

  • Un vettore (Vector): un array di elementi che hanno per lo più la stessa semantica. L'insieme delle firme associate a un elemento dati firmato è una tipica applicazione di un vettore.

  • Un record (Record): un array i cui elementi hanno una semantica diversa, definita in base alla posizione, come dettagliato nella definizione della struttura dati. Un punto 2D, specificato come un array di una coordinata x (che viene per prima) e una coordinata y (che viene per seconda), è un esempio di record, così come la coppia di esponente (primo) e mantissa (secondo) in una frazione decimale CBOR.

  • Una tabella (Table): una mappa da un dominio di chiavi di mappa a un dominio di valori di mappa, che hanno per lo più la stessa semantica. Un insieme di tag di lingua, ciascuno mappato a una stringa di testo tradotta in quella lingua specifica, è un esempio di tabella. Il dominio delle chiavi non è solitamente limitato a un insieme specifico dalla specifica ma è aperto per l'applicazione, ad esempio, in una tabella che mappa gli indirizzi IP agli indirizzi MAC (Media Access Control), la specifica non tenta di prevedere tutti i possibili indirizzi IP. In un linguaggio come JavaScript, una "Map" (in contrapposizione a un semplice "Object") verrebbe spesso impiegata per ottenere la generalità del dominio delle chiavi.

  • Una struttura (Struct): una mappa da un dominio di chiavi di mappa come definito dalla specifica a un dominio di valori di mappa la cui semantica di ciascuno è legata a una specifica chiave di mappa. Questo è ciò che molte persone hanno in mente quando pensano agli oggetti JSON; CBOR aggiunge la capacità di utilizzare chiavi di mappa che non sono solo stringhe di testo. Le strutture possono essere utilizzate per risolvere problemi simili a quelli per cui vengono utilizzati i record; l'uso di chiavi di mappa esplicite facilita l'opzionalità e l'estensibilità.

Due concetti importanti forniscono le basi per CDDL:

  1. Invece di definire tutti e quattro i tipi di composizione in CDDL separatamente, o anche definire un tipo per gli array (vettori e record) e un tipo per le mappe (tabelle e strutture), c'è solo un tipo di composizione in CDDL: il gruppo (Sezione 2.1).

  2. L'altro concetto importante è quello di un tipo. L'intera specifica CDDL definisce un tipo (quello definito dalla sua prima regola), che formalmente è l'insieme di elementi dati CBOR che sono accettabili come "istanze" per questa specifica. CDDL predefinisce un certo numero di tipi di base come "uint" (intero senza segno) o "tstr" (stringa di testo), facendo spesso uso di una semplice notazione formale per gli elementi dati CBOR. Ogni valore che può essere espresso come un elemento dati CBOR è anche un tipo a sé stante, ad esempio, "1". Un tipo può essere costruito come una scelta di altri tipi, ad esempio, un "int" è un "uint" o un "nint" (intero negativo). Infine, un tipo può essere costruito come un array o una mappa da un gruppo.

Il resto di questa sezione introduce un certo numero di concetti di base di CDDL e la Sezione 3 definisce una sintassi aggiuntiva. L'Appendice C fornisce un riepilogo conciso della semantica di CDDL.

2.1. Gruppi e Composizione in CDDL

I gruppi CDDL sono elenchi di voci di gruppo, ognuna delle quali può essere una coppia nome/valore o un'espressione di gruppo più complessa (che a sua volta rappresenta una sequenza di coppie nome/valore). Un gruppo CDDL è una produzione in una grammatica che corrisponde a determinate sequenze di coppie nome/valore ma non ad altre. La grammatica si basa sui concetti delle Parsing Expression Grammar (PEG) (vedere Appendice A).

In un contesto di array, viene rappresentato solo il valore della coppia nome/valore; il nome è solo un'annotazione (e può essere omesso dalla specifica del gruppo se non necessario). In un contesto di mappa, i nomi diventano le chiavi della mappa ("chiavi membro").

In un contesto di array, la sequenza effettiva degli elementi nel gruppo è importante, poiché tale sequenza è l'informazione che consente di associare gli elementi effettivi dell'array alle voci nel gruppo. In un contesto di mappa, la sequenza delle voci in un gruppo non è rilevante (ma c'è ancora bisogno di scrivere le voci del gruppo in una sequenza).

Un array corrisponde a una specifica data come gruppo quando il gruppo corrisponde a una sequenza di coppie nome/valore le cui parti valore corrispondono esattamente agli elementi dell'array nell'ordine.

Una mappa corrisponde a una specifica data come gruppo quando il gruppo corrisponde a una sequenza di coppie nome/valore tale che tutte queste coppie nome/valore siano presenti nella mappa e la mappa non abbia alcuna coppia nome/valore non coperta dal gruppo.

Un semplice esempio di utilizzo diretto di un gruppo in una definizione di mappa è:

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

Figura 1: Utilizzo diretto di un gruppo in una mappa

Le tre voci del gruppo sono scritte tra le parentesi graffe che creano la mappa: qui, "age", "name" e "employer" sono i nomi che si trasformano nelle stringhe di testo della chiave della mappa, e "int" e "tstr" (stringa di testo) sono i tipi dei valori della mappa sotto queste chiavi.

Un gruppo da solo (senza creare una mappa attorno ad esso) può essere inserito tra parentesi (tonde) e gli può essere assegnato un nome utilizzandolo in una regola:

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

Figura 2: Un gruppo di base

Questa definizione di gruppo separata e denominata ci consente di riformulare la Figura 1 come:

person = {
pii
}

Figura 3: Utilizzo di un gruppo per nome

Si noti che le parentesi (graffe) indicano la creazione di una mappa; i gruppi stessi sono neutri rispetto al fatto che verranno utilizzati in una mappa o in un array.

Come mostrato nella Figura 1, le parentesi per i gruppi sono facoltative quando è presente un altro set di parentesi. Si noti che possono ancora essere utilizzate, portando a questo esempio non così realistico, ma perfettamente valido:

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

Figura 4: Utilizzo di un gruppo tra parentesi in una mappa

I gruppi possono essere utilizzati per fattorizzare parti comuni di strutture, ad esempio, invece di scrivere specifiche in stile copia/incolla, come nella Figura 5, si può fattorizzare il sottogruppo comune, scegliere un nome per esso e scrivere solo le parti specifiche nelle singole mappe (Figura 6).

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

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

Figura 5: Mappe con Copia/Incolla

person = {
identity,
employer: tstr,
}

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

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

Figura 6: Utilizzo di un gruppo per la fattorizzazione

Si noti che gli elenchi all'interno delle parentesi graffe nelle definizioni precedenti costituiscono gruppi (anonimi), mentre "identity" è un gruppo denominato, che può quindi essere incluso come parte di altri gruppi (anonimi come nell'esempio, o essi stessi denominati).

2.1.1. Utilizzo

I gruppi sono lo strumento utilizzato nella composizione delle strutture dati con CDDL. È una questione di stile nel definire tali strutture se definire i gruppi (in modo anonimo) direttamente nei loro contesti o se definirli in una regola separata e fare riferimento ad essi con il loro rispettivo nome (possibilmente più di una volta).

Con questo, è consentito definire tutte le piccole parti delle proprie strutture dati e comporre unità di dati di protocollo più grandi con quelle o avere solo una grande unità di dati di protocollo che ha tutte le definizioni ad hoc dove necessario.

2.1.2. Sintassi

La sintassi di composizione è intesa per essere concisa e facile da leggere:

  • L'inizio e la fine di un gruppo possono essere contrassegnati da "(" e ")".

  • Le definizioni delle voci all'interno di un gruppo sono annotate come segue: keytype => valuetype, (leggi "keytype mappa a valuetype"). La virgola è in realtà facoltativa (non solo nella voce finale), ma è considerato un buono stile metterla. La doppia freccia può essere sostituita da due punti nel caso comune di utilizzo diretto di una stringa di testo o di un letterale intero come chiave; vedere la Sezione 3.5.1. Questo è anche il modo comune di denominare gli elementi di un array solo per la documentazione; vedere la Sezione 3.4.

Una voce di base è costituita da un keytype e un valuetype, entrambi tipi (Sezione 2.2); questa voce corrisponde a qualsiasi coppia nome/valore il cui nome è nel keytype e il cui valore è nel valuetype.

Un gruppo definito come una sequenza di voci di gruppo corrisponde a qualsiasi sequenza di coppie nome/valore composta dalla concatenazione nell'ordine di ciò che le voci corrispondono.

Una definizione di gruppo può anche contenere scelte tra gruppi; vedere la Sezione 2.2.2.

2.2. Tipi

2.2.1. Valori

Valori come numeri e stringhe possono essere utilizzati al posto di un tipo. (Ad esempio, questa è una cosa molto comune da fare per un tipo di chiave, abbastanza comune che CDDL fornisce una sintassi di convenienza aggiuntiva per questo.)

La notazione dei valori si basa sul linguaggio C, ma non offre tutte le variazioni sintattiche (vedere l'Appendice B per i dettagli). La notazione dei valori per i numeri eredita da C la distinzione tra valori interi (nessuna parte frazionaria o esponente fornito -- NR1 [ISO6093]; "NR" sta per "rappresentazione numerica") e valori in virgola mobile (dove sono presenti una parte frazionaria, un esponente o entrambi -- NR2 o NR3), quindi il tipo "1" non include alcun numero in virgola mobile mentre i tipi "1e3" e "1.5" sono entrambi numeri in virgola mobile e non includono alcun numero intero.

2.2.2. Scelte (Choices)

Molti punti che consentono un tipo consentono anche una scelta tra tipi, delimitata da una "/" (barra). L'intero costrutto di scelta può essere inserito tra parentesi se ciò è necessario per rendere la costruzione non ambigua (vedere l'Appendice B per i dettagli della grammatica CDDL).

Le scelte di valori possono essere utilizzate per esprimere enumerazioni:

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

Analogamente ai tipi, CDDL consente anche scelte tra gruppi, delimitate da un "//" (doppia barra). Si noti che l'operatore "//" lega molto più debolmente rispetto agli altri operatori CDDL, quindi ogni riga all'interno di "delivery" nell'esempio seguente è la sua alternativa nella scelta del gruppo:

address = { delivery }

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

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

Una scelta di gruppo corrisponde all'unione degli insiemi di sequenze di coppie nome/valore che le alternative nella scelta possono.

Sia per le scelte di tipo che per le scelte di gruppo, è possibile aggiungere alternative aggiuntive a una regola in un secondo momento in regole separate utilizzando rispettivamente "/=" e "//=", invece di "=":

attire /= "swimwear"

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

Non è un errore se un nome viene utilizzato per la prima volta con un "/=" o "//=" (non è necessario "crearlo" con "=").

2.2.2.1. Intervalli (Ranges)

Invece di nominare tutti i valori che compongono una scelta, CDDL consente di costruire un intervallo da due valori che sono in una relazione di ordinamento: un limite inferiore (primo valore) e un limite superiore (secondo valore). Un intervallo può includere entrambi i limiti dati (indicato unendo due valori con ".."), oppure può includere il limite inferiore ed escludere il limite superiore (indicato utilizzando invece "..."). Se il limite inferiore supera il limite superiore, il tipo risultante è l'insieme vuoto (questo comportamento può essere desiderabile quando vengono utilizzati i generici (Sezione 3.10)).

device-address = byte
max-byte = 255
byte = 0..max-byte ; intervallo inclusivo
first-non-byte = 256
byte1 = 0...first-non-byte ; byte1 è equivalente a byte

CDDL attualmente consente solo intervalli tra interi (che corrispondono a valori interi) o tra valori in virgola mobile (che corrispondono a valori in virgola mobile). Se entrambi sono necessari in un tipo, è possibile utilizzare (goffamente) una scelta di tipo tra i due tipi di intervalli:

int-range = 0..10 ; corrispondono solo interi
float-range = 0.0..10.0 ; corrispondono solo float
BAD-range1 = 0..10.0 ; NON DEFINITO
BAD-range2 = 0.0..10 ; NON DEFINITO
numeric-range = int-range / float-range

(Vedere anche gli operatori di controllo .lt/.ge e .le/.gt nella Sezione 3.8.6.)

Si noti che il punto è un carattere di continuazione del nome valido in CDDL, quindi

min..max

non è un'espressione di intervallo ma un singolo nome. Quando si utilizza un nome come lato sinistro di un operatore di intervallo, utilizzare la spaziatura come in

min .. max

per separare l'operatore di intervallo.

2.2.2.2. Trasformare un gruppo in una scelta

Alcune scelte sono costruite da grandi numeri di valori, spesso interi, a ciascuno dei quali è meglio dare un nome semantico nella specifica. Invece di nominare ciascuno di questi interi e quindi accumularli in una scelta, CDDL consente di costruire una scelta da un gruppo prefissandolo con un carattere "&":

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,
)

Come con l'uso dei gruppi negli array (Sezione 3.4), i nomi dei membri hanno solo valore documentale (in particolare, potrebbero essere utilizzati da uno strumento quando visualizza numeri interi presi da quella scelta).

2.2.3. Tipi di rappresentazione

CDDL consente la specifica di un tipo di elemento dati facendo riferimento alla rappresentazione CBOR (in particolare, ai tipi maggiori e alle informazioni aggiuntive; vedere la Sezione 2 di [RFC7049]). Il modo in cui viene utilizzato dovrebbe essere evidente dal preludio (Appendice D): un segno di cancelletto ("#") seguito facoltativamente da un numero da 0 a 7 che identifica il tipo maggiore, che può quindi essere seguito da un punto e da un numero che specifica l'informazione aggiuntiva. Questa costruzione specifica l'insieme di valori che possono essere serializzati in CBOR (cioè "any"), dal tipo maggiore dato se ne viene dato uno, o dal tipo maggiore dato con l'informazione aggiuntiva se vengono dati entrambi. Dove viene utilizzato un tipo maggiore di 6 (Tag), il tipo dell'elemento taggato può essere specificato aggiungendolo tra parentesi.

Si noti che sebbene questa notazione sia basata sulla serializzazione CBOR, riguarda un insieme di valori a livello di modello dati, ad esempio, "#7.25" specifica l'insieme di valori che possono essere rappresentati come float a mezza precisione; non impone che questi valori debbano essere serializzati anche come float a mezza precisione: CDDL non fornisce alcun mezzo linguistico per limitare la scelta delle varianti di serializzazione. Ciò consente anche l'uso di CDDL con JSON, che utilizza un modo fondamentalmente diverso di serializzare (alcuni del) gli stessi valori.

Potrebbe essere necessario utilizzare i tipi di rappresentazione al di fuori del preludio, ad esempio, una specifica potrebbe iniziare utilizzando un tag esistente in un modo più specifico o potrebbe definire un nuovo tag non definito nel preludio:

my_breakfast = #6.55799(breakfast)   ; cbor-any è troppo generale!
breakfast = cereal / porridge
cereal = #6.998(tstr)
porridge = #6.999([liquid, solid])
liquid = milk / water
milk = 0
water = 1
solid = tstr

2.2.4. Tipo radice (Root Type)

Non esiste una sintassi speciale per identificare la radice di una definizione di struttura dati CDDL: tale ruolo è semplicemente assunto dalla prima regola definita nel file.

Ciò è motivato dal solito approccio top-down per la definizione delle strutture dati, decomponendo una grande unità di struttura dati in parti più piccole; tuttavia, tranne che per il tipo radice, non c'è bisogno di seguire rigorosamente questa sequenza.

(Si noti che non c'è modo di utilizzare un gruppo come radice -- deve essere un tipo.)