Passa al contenuto principale

3. Sintassi

In questa sezione viene presentata la sintassi complessiva di CDDL, insieme ad alcuni esempi che illustrano semplicemente la sintassi. (La definizione non tenta di essere eccessivamente formale; fare riferimento all'Appendice B per i dettagli.)

3.1. Convenzioni generali

La sintassi di base è ispirata ad ABNF [RFC5234], con le seguenti caratteristiche:

  • Le regole, sia che definiscano gruppi o tipi, sono definite con un nome, seguito da un segno di uguale "=" e dalla definizione effettiva secondo le rispettive regole sintattiche di quella definizione.

  • Un nome può essere composto da qualsiasi carattere dell'insieme {"A" a "Z", "a" a "z", "0" a "9", "", "-", "@", ".", "$"}, iniziando con un carattere alfabetico (inclusi "@", "", "$") e terminando con un tale carattere o una cifra.

    • I nomi distinguono tra maiuscole e minuscole.
    • Lo stile preferito è iniziare un nome con una lettera minuscola.
    • Il trattino è preferito al trattino basso (tranne in una "bareword" (Sezione 3.5.1), dove la semantica potrebbe effettivamente richiedere un trattino basso).
    • Il punto può essere utile per specifiche più grandi, per esprimere una struttura modulare (come in "tcp.throughput" vs "udp.throughput").
    • Un certo numero di nomi sono predefiniti nel preludio CDDL, come elencato nell'Appendice D.
    • I nomi delle regole (tipi o gruppi) non appaiono nell'effettiva codifica CBOR, ma i nomi usati come "barewords" nelle chiavi membro sì.
  • I commenti iniziano con un carattere ";" (punto e virgola) e terminano alla fine di una riga (LF o CRLF).

  • Tranne che all'interno delle stringhe, lo spazio bianco (spazi, nuove righe e commenti) è usato per separare gli elementi sintattici per leggibilità (e per separare identificatori, operatori di intervallo o numeri che si susseguono); altrimenti è del tutto facoltativo.

  • I numeri esadecimali sono preceduti da "0x" (senza virgolette) e non distinguono tra maiuscole e minuscole. Allo stesso modo, i numeri binari sono preceduti da "0b".

  • Le stringhe di testo sono racchiuse tra doppi apici '"'. Seguono le convenzioni per le stringhe come definito nella Sezione 7 di [RFC8259]. (Gli utenti di ABNF potrebbero voler notare che in CDDL non c'è supporto per il concetto di insensibilità alle maiuscole e minuscole per le stringhe di testo; se necessario, possono essere usate espressioni regolari (Sezione 3.8.3).)

  • Le stringhe di byte sono racchiuse tra singoli apici "'" e possono essere prefissate da "h" o "b64". Se non prefissata, la stringa viene interpretata come una stringa di testo, tranne per il fatto che i singoli apici devono essere sottoposti a escape e i byte UTF-8 risultanti sono contrassegnati come stringa di byte (tipo maggiore 2). Se prefissata da "h" o "b64", la stringa viene interpretata come una sequenza di coppie di cifre esadecimali (base16; vedi Sezione 8 di [RFC4648]) o una stringa base64(url) (Sezione 4 o Sezione 5 di [RFC4648]), rispettivamente (come con la notazione diagnostica nella Sezione 6 di [RFC7049]; cfr. Appendice G.2); qualsiasi spazio bianco presente all'interno della stringa (inclusi i commenti) viene ignorato nel caso prefissato.

  • CDDL usa UTF-8 [RFC3629] per la sua codifica. L'elaborazione di CDDL non comporta processi di normalizzazione Unicode.

Esempio:

; Questo è un commento
person = { g }

g = (
"name": tstr,
age: int, ; "age" è una bareword
)

3.2. Occorrenza (Occurrence)

Un indicatore di occorrenza facoltativo può essere dato prima di una voce di gruppo. È (1) uno dei caratteri "?" (facoltativo), "" (zero o più), o "+" (uno o più), oppure (2) della forma nm, dove n e m sono interi senza segno facoltativi e n è il limite inferiore (predefinito 0) e m è il limite superiore (predefinito nessun limite) delle occorrenze.

Se non viene specificato alcun indicatore di occorrenza, la voce di gruppo deve ricorrere esattamente una volta (come se fosse specificato 1*1). Una voce di gruppo con un indicatore di occorrenza corrisponde a sequenze di coppie nome/valore che sono composte concatenando un numero di sequenze a cui corrisponde la voce di gruppo di base, dove quel numero deve essere consentito dall'indicatore di occorrenza.

Si noti che CDDL, al di fuori di direttive/annotazioni che potrebbero essere eventualmente definite, non fa alcuna prescrizione sul fatto che array o mappe utilizzino codifiche a lunghezza definita o indefinita. Cioè, non c'è correlazione tra lasciare la dimensione di un array "aperta" nella specifica e il fatto che sia poi scambiato con lunghezza definita o indefinita.

Si prega inoltre di notare che CDDL può descrivere una flessibilità che il modello dati della rappresentazione di destinazione non ha. Questo è abbastanza ovvio per JSON ma è rilevante anche per CBOR:

apartment = {
kitchen: size,
* bedroom: size,
}
size = float ; in m2

La specifica precedente non significa che CBOR venga modificato per consentire l'uso della chiave "bedroom" più di una volta. In altre parole, a causa delle restrizioni imposte dal modello dati, la terza riga diventa praticamente:

? bedroom: size,

(Gli indicatori di occorrenza oltre l'uno sono ancora utili nelle mappe per gruppi che consentono una varietà di chiavi.)

3.3. Nomi predefiniti per i tipi

CDDL predefinisce un certo numero di nomi. Questa sottosezione riassume questi nomi, ma si prega di consultare l'Appendice D per le definizioni esatte.

Sono definite le seguenti parole chiave per i tipi di dati primitivi:

"bool": Valore booleano (tipo maggiore 7, informazione aggiuntiva 20 o 21).

"uint": Un intero senza segno (tipo maggiore 0).

"nint": Un intero negativo (tipo maggiore 1).

"int": Un intero senza segno o un intero negativo.

"float16": Un numero rappresentabile come un float a mezza precisione [IEEE754] (tipo maggiore 7, informazione aggiuntiva 25).

"float32": Un numero rappresentabile come un float a singola precisione [IEEE754] (tipo maggiore 7, informazione aggiuntiva 26).

"float64": Un numero rappresentabile come un float a doppia precisione [IEEE754] (tipo maggiore 7, informazione aggiuntiva 27).

"float": Uno tra float16, float32 o float64.

"bstr" o "bytes": Una stringa di byte (tipo maggiore 2).

"tstr" o "text": Una stringa di testo (tipo maggiore 3).

(Si noti che non ci sono nomi predefiniti per array o mappe; questi sono definiti con la sintassi data di seguito.)

Inoltre, nel preludio sono definiti un certo numero di tipi che sono associati a tag CBOR, come "tdate", "bigint", "regexp", ecc.

3.4. Array

Le definizioni di array racchiudono un gruppo con parentesi quadre.

Per ogni voce, è consentito un indicatore di occorrenza come specificato nella Sezione 3.2.

Per esempio:

unlimited-people = [* person]
one-or-two-people = [1*2 person]
at-least-two-people = [2* person]
person = (
name: tstr,
age: uint,
)

Il gruppo "person" è definito in modo tale che ripeterlo nell'array genera ogni volta nomi ed età alternati, quindi questi sono quattro valori validi per un elemento dati di tipo "unlimited-people":

["roundlet", 1047, "psychurgy", 2204, "extrarhythmical", 2231]
[]
["aluminize", 212, "climograph", 4124]
["penintime", 1513, "endocarditis", 4084, "impermeator", 1669,
"coextension", 865]

3.5. Mappe (Maps)

La sintassi per specificare le mappe merita un'attenzione particolare, insieme a una serie di ottimizzazioni e comodità, poiché è probabile che sia il punto focale di molte specifiche che utilizzano CDDL. Mentre la sintassi non distingue rigorosamente l'uso di struct e l'uso di tabella delle mappe, si rivolge specificamente a ciascuno di essi.

Ma prima, ribadiamo una caratteristica di CBOR che ha ereditato da JSON: le coppie chiave/valore nelle mappe CBOR non hanno un ordine fisso. (Si potrebbero immaginare situazioni in cui fissare l'ordine potrebbe essere utile. Ad esempio, un decodificatore potrebbe cercare valori legati alle chiavi intere 1, 3 e 7. Se l'ordine fosse fisso e il decodificatore incontra la chiave 4 senza aver incontrato la chiave 3, potrebbe concludere che la chiave 3 non è disponibile senza fare una contabilità più complicata. Sfortunatamente, né JSON né CBOR supportano questo, quindi non è stato fatto alcun tentativo di supportarlo nemmeno in CDDL.)

3.5.1. Strutture (Structs)

L'uso "struct" delle mappe è simile al modo in cui gli oggetti JSON sono usati in molte applicazioni JSON.

Una mappa è definita nello stesso modo (vedi Sezione 3.4) per definire un array, tranne per l'uso di parentesi graffe "{}" invece di parentesi quadre "[]".

Un indicatore di occorrenza come specificato nella Sezione 3.2 è consentito per ogni voce di gruppo.

Quello che segue è un esempio di un record con una struttura incorporata:

Geography = [
city : tstr,
gpsCoordinates : GpsCoordinates,
]

GpsCoordinates = {
longitude : uint, ; gradi, scalati di 10^7
latitude : uint, ; gradi, scalati di 10^7
}

Durante la codifica, il record Geography è codificato usando un array CBOR con due membri (le chiavi per le voci di gruppo sono ignorate), mentre la struttura GpsCoordinates è codificata come una mappa CBOR con due coppie chiave/valore.

I tipi usati in una struttura possono essere definiti in regole separate o semplicemente sul posto (potenzialmente messi tra parentesi, come per le scelte). Per esempio:

located-samples = {
sample-point: int,
samples: [+ float],
}

dove "located-samples" è il tipo di dati da usare quando si fa riferimento alla struct, e "sample-point" e "samples" sono le chiavi da usare. Questo è in realtà un esempio completo: un identificatore seguito da due punti può essere usato direttamente come stringa di testo per una chiave membro (parliamo di una chiave membro "bareword"), così come può esserlo una stringa tra doppi apici o un numero. (Quando altri tipi -- in particolare, tipi che contengono più di un valore -- sono usati come tipi di chiavi, sono seguiti da una doppia freccia; vedi sotto.)

Se una chiave stringa di testo non corrisponde alla sintassi per un identificatore (o se lo specificatore preferisce semplicemente usare i doppi apici), la sintassi della stringa di testo può essere usata anche nella posizione della chiave membro, seguita da due punti. L'esempio sopra potrebbe quindi essere stato scritto con stringhe citate nelle posizioni delle chiavi membro.

Più in generale, i tipi specificati in un modo diverso da quelli elencati per i casi sopra descritti possono essere usati in una posizione di tipo chiave facendoli seguire da una doppia freccia -- in particolare, la doppia freccia è necessaria se un tipo è denominato da un identificatore (che, quando seguito da due punti, sarebbe interpretato come una "bareword" e trasformato in una stringa di testo). Una stringa di testo letterale dà luogo anche a un tipo (che contiene solo un singolo valore -- la stringa data), quindi un'altra forma per questo esempio è:

located-samples = {
"sample-point" => int,
"samples" => [+ float],
}

Vedi la Sezione 3.5.4 sotto su come la scorciatoia dei due punti (":") descritta qui aggiunge anche una semantica implicita.

Un modo migliore per dimostrare l'uso della doppia freccia potrebbe essere:

located-samples = {
sample-point: int,
samples: [+ float],
* equipment-type => equipment-tolerances,
}
equipment-type = [name: tstr, manufacturer: tstr]
equipment-tolerances = [+ [float, float]]

L'esempio seguente definisce una struct con voci facoltative: nome visualizzato (come stringa di testo), i componenti del nome nome e cognome (come stringhe di testo) e informazioni sull'età (come intero senza segno).

PersonalData = {
? displayName: tstr,
NameComponents,
? age: uint,
}

NameComponents = (
? firstName: tstr,
? familyName: tstr,
)

Si noti che la definizione del gruppo per NameComponents non genera un'altra mappa; invece, tutte e quattro le chiavi sono direttamente nella struct costruita da PersonalData.

In questo esempio, tutte le coppie chiave/valore sono facoltative dal punto di vista di CDDL. Senza un indicatore di occorrenza, una voce è obbligatoria.

Se si desidera aggiungere altre voci non specificate dalla specifica attuale, questa possibilità può essere aggiunta esplicitamente:

PersonalData = {
? displayName: tstr,
NameComponents,
? age: uint,
* tstr => any
}

NameComponents = (
? firstName: tstr,
? familyName: tstr,
)

Figura 7: Dati personali: Esempio di estensibilità

Lo strumento CDDL descritto nell'Appendice F ha generato quanto segue come istanza accettabile per questa specifica:

{"familyName": "agust", "antiforeignism": "pretzel",
"springbuck": "illuminatingly", "exuviae": "ephemeris",
"kilometrage": "frogfish"}

(Vedi la Sezione 3.9 per un modo per identificare esplicitamente un punto di estensione.)

3.5.2. Tabelle (Tables)

Una tabella può essere specificata definendo una mappa con voci in cui il tipo di chiave consente più di un singolo valore; per esempio:

square-roots = {* x => y}
x = int
y = float

Qui, la chiave in ogni coppia chiave/valore ha il tipo di dati x (definito come int), e il valore ha il tipo di dati y (definito come float).

Se la specifica non ha bisogno di limitare uno tra x o y (cioè, l'applicazione è libera di scegliere per voce), può essere sostituito dal nome predefinito "any".

Come altro esempio, quanto segue potrebbe essere usato come tabella di conversione da un intero o float a una stringa:

tostring = {* mynumber => tstr}
mynumber = int / float

3.5.3. Ordine non deterministico

Mentre il modo in cui gli array vengono abbinati è completamente determinato dal formalismo PEG (vedi Appendice A), l'abbinamento è più complicato per le mappe, poiché le mappe non hanno un ordine intrinseco. Per ogni coppia nome/valore candidata che l'algoritmo PEG proverebbe, viene scelto un membro corrispondente dall'intera mappa. Per alcune espressioni di gruppo, più di un membro nella mappa potrebbe corrispondere. Molto spesso, questo è irrilevante, poiché l'espressione di gruppo tende a consumare tutte le corrispondenze:

labeled-values = {
? fritz: number,
* label => value
}
label = text
value = number

Qui, se è presente un membro con la chiave "fritz", questo verrà scelto dalla prima voce del gruppo; tutti i restanti membri testo/numero verranno scelti dalla seconda voce (e se rimane qualcosa non scelto, la mappa non corrisponde).

Tuttavia, è possibile costruire espressioni di gruppo in cui ciò che viene effettivamente scelto è indeterminato ma conta:

do-not-do-this = {
int => int,
int => 6,
}

Quando questa espressione viene abbinata a "{3: 5, 4: 6}", la prima voce di gruppo potrebbe scegliere "3: 5", lasciando "4: 6" per l'abbinamento della seconda. Oppure potrebbe scegliere "4: 6", non lasciando nulla per la seconda voce. Questo non determinismo patologico è causato dallo specificare "più generale" prima di "più specifico" e dall'avere una regola generale che consuma solo un sottoinsieme delle coppie chiave/valore della mappa che è in grado di abbinare -- entrambi tendono a non verificarsi nelle specifiche effettive delle mappe. Al momento della scrittura, gli strumenti CDDL non possono rilevare tali casi automaticamente e per la presente versione della specifica CDDL, allo scrittore della specifica viene semplicemente chiesto di non scrivere specifiche patologicamente non deterministiche.

(Il lettore attento ricorderà quelli che erano chiamati "modelli di contenuto ambigui" nello Standard Generalized Markup Language (SGML) e "modelli di contenuto non deterministici" in XML. Questo problema è correlato a quello descritto qui, ma il problema qui è specificamente causato dalla mancanza di ordine nelle mappe, qualcosa con cui i linguaggi di schema XML non devono confrontarsi. Si noti che il pattern "interleave" di RELAX NG gestisce esplicitamente la mancanza di ordine sul lato della specifica, mentre le istanze in XML hanno sempre un ordine determinato.)

3.5.4. Tagli nelle mappe (Cuts)

L'idioma di estensibilità discusso sopra per le struct ha un problema:

extensible-map-example = {
? "optional-key" => int,
* tstr => any
}

In questo esempio, c'è una chiave facoltativa "optional-key", che, quando presente, mappa a un intero. C'è anche un jolly per qualsiasi aggiunta futura.

Sfortunatamente, l'elemento dati

{ "optional-key": "nonsense" }

corrisponde a questa specifica: mentre la prima voce del gruppo non corrisponde, la seconda (il jolly) lo fa. Questo potrebbe essere molto desiderabile (ad esempio, se a un'estensione futura è consentito estendere il tipo di "optional-key"), ma in molti casi non lo è.

In previsione di una potenziale funzionalità più generale chiamata "tagli" (cuts), CDDL consente di inserire un taglio "^" nella definizione della voce di mappa:

extensible-map-example = {
? "optional-key" ^ => int,
* tstr => any
}

Un taglio in questa posizione significa che una volta che la chiave membro corrisponde alla parte del nome di una voce che porta un taglio, altre potenziali corrispondenze per la chiave di quel membro che si verificano in voci successive nel gruppo della mappa non sono più consentite. In altre parole, quando una voce di gruppo sceglierebbe una coppia chiave/valore basata solo su una chiave corrispondente, "blocca" la scelta -- questa regola si applica, indipendentemente dal fatto che anche il valore corrisponda, quindi quando non corrisponde, l'intera mappa fallisce la corrispondenza. In sintesi, l'esempio sopra non corrisponde più alla specifica modificata con il taglio.

Poiché il desiderio per questo tipo di corrispondenza esclusiva è così frequente, la scorciatoia ":" è in realtà definita per includere la semantica del taglio. Pertanto, l'esempio precedente (incluso il taglio) può essere scritto più semplicemente come:

extensible-map-example = {
? "optional-key": int,
* tstr => any
}

o ancora più breve, usando una bareword per la chiave:

extensible-map-example = {
? optional-key: int,
* tstr => any
}

3.6. Tag

Un tipo può fare uso di un tag CBOR (tipo maggiore 6) usando la notazione del tipo di rappresentazione, dando #6.nnn(type) dove nnn è un intero senza segno che dà il numero del tag e "type" è il tipo dell'elemento dati etichettato.

Per esempio, la seguente riga dal preludio CDDL (Appendice D) definisce "biguint" come nome di tipo per un bignum senza segno N:

biguint = #6.2(bstr)

I tag definiti da [RFC7049] sono inclusi nel preludio. Tag aggiuntivi registrati da quando è stato scritto [RFC7049] devono essere aggiunti a una specifica CDDL secondo necessità; ad esempio, un tag binario Universally Unique Identifier (UUID) potrebbe essere referenziato come "buuid" in una specifica dopo aver definito

buuid = #6.37(bstr)

Nell'esempio seguente, l'uso del tag 32 per gli URI è facoltativo:

my_uri = #6.32(tstr) / tstr

3.7. Disimballaggio (Unwrapping)

Il gruppo che viene utilizzato per definire una mappa o un array può spesso essere riutilizzato nella definizione di un'altra mappa o array. Allo stesso modo, un tipo definito come tag trasporta un elemento dati interno a cui si vorrebbe fare riferimento. In questi casi, è opportuno utilizzare semplicemente il nome del tipo di mappa, array o tag come handle per il gruppo o il tipo definito al suo interno.

L'operatore "unwrap" (scritto anteponendo un carattere tilde "~" a un nome) può essere utilizzato per spogliare il tipo definito per un nome di uno strato, esponendo il gruppo sottostante (per mappe e array) o il tipo (per i tag).

Ad esempio, un'applicazione potrebbe voler definire un'intestazione di base e un'intestazione avanzata. Senza disimballaggio, questo potrebbe essere fatto così:

basic-header-group = (
field1: int,
field2: text,
)

basic-header = [ basic-header-group ]

advanced-header = [
basic-header-group,
field3: bytes,
field4: number, ; come nel tipo etichettato "time"
]

Il disimballaggio lo semplifica in:

basic-header = [
field1: int,
field2: text,
]

advanced-header = [
~basic-header,
field3: bytes,
field4: ~time,
]

(Si noti che omettere il primo operatore di disimballaggio in quest'ultimo esempio porterebbe ad annidare il basic-header nel suo stesso array all'interno di advanced-header, mentre, con il basic-header disimballato, la definizione del gruppo all'interno di basic-header è essenzialmente ripetuta all'interno di advanced-header, portando a un singolo array. Questo può essere usato per varie applicazioni spesso risolte dall'ereditarietà nei linguaggi di programmazione. L'effetto del disimballaggio può anche essere descritto come "infilare" il gruppo o il tipo all'interno del tipo referenziato, il che ha suggerito il carattere "~" simile a un filo.)

3.8. Controlli (Controls)

Un controllo consente di correlare un tipo bersaglio con un tipo controllore tramite un operatore di controllo.

La sintassi per un tipo di controllo è "target .control-operator controller", dove gli operatori di controllo sono identificatori speciali preceduti da un punto. (Si noti che target o controller potrebbero dover essere messi tra parentesi.)

A questo punto sono definiti un certo numero di operatori di controllo. Ulteriori operatori di controllo possono essere definiti da nuove versioni di questa specifica o registrandoli secondo le procedure nella Sezione 6.1.

3.8.1. Operatore di controllo .size

Un controllo ".size" controlla la dimensione del bersaglio in byte tramite il tipo di controllo. Il controllo è definito per le stringhe di testo e di byte, dove controlla direttamente il numero di byte nella stringa. È definito anche per gli interi senza segno (vedi sotto). La Figura 8 mostra un esempio di utilizzo per le stringhe di byte.

full-address = [[+ label], ip4, ip6]
ip4 = bstr .size 4
ip6 = bstr .size 16
label = bstr .size (1..63)

Figura 8: Controllo per la dimensione in byte

Quando applicato a un intero senza segno, il controllo ".size" limita l'intervallo di quell'intero fornendo un numero massimo di byte che dovrebbero essere necessari in una rappresentazione informatica di quell'intero senza segno. In altre parole, "uint .size N" è equivalente a "0...BYTES_N", dove BYTES_N == 256**N.

audio_sample = uint .size 3 ; 24-bit, equivalente a 0...16777216

Figura 9: Controllo della dimensione intera in byte

Si noti che, come per le restrizioni di valore in CDDL, questo controllo non è un vincolo di rappresentazione; un numero che si adatta a meno byte può ancora essere rappresentato in quella forma, e un'implementazione inefficiente potrebbe usare una forma più lunga (a meno che ciò non sia limitato da alcuni vincoli di formato al di fuori di CDDL, come le regole nella Sezione 3.9 di [RFC7049]).

3.8.2. Operatore di controllo .bits

Un controllo ".bits" su una stringa di byte indica che, nel bersaglio, possono essere impostati solo i bit numerati da un numero nel tipo di controllo. (I bit sono contati nel solito modo, con il bit numero "n" impostato in "str" che significa "(str[n >> 3] & (1 << (n & 7))) != 0".) Allo stesso modo, un controllo ".bits" su un intero senza segno "i" indica che per tutti gli interi senza segno "n" dove "(i & (1 << n)) != 0", "n" deve essere nel tipo di controllo.

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) ; bit di offset dati

rwxbits = uint .bits rwx
rwx = &(r: 2, w: 1, x: 0)

Figura 10: Controllo dei bit che possono essere impostati

Lo strumento CDDL descritto nell'Appendice F genera le seguenti dieci istanze di esempio per "tcpflagbytes":

h'906d' h'01fc' h'8145' h'01b7' h'013d' h'409f' h'018e' h'c05f'
h'01fa' h'01fe'

Questi esempi non illustrano che la specifica CDDL sopra non specifica esplicitamente una dimensione di due byte: un'istanza valida "tutto libero" dei byte di flag potrebbe essere anche "h''" o "h'00'" o anche "h'000000'".

3.8.3. Operatore di controllo .regexp

Un controllo ".regexp" indica che la stringa di testo data come bersaglio deve corrispondere all'espressione regolare XML Schema Definition (XSD) data come valore nel tipo di controllo. Le espressioni regolari XSD sono definite nell'Appendice F di [W3C.REC-xmlschema-2-20041028].

nai = tstr .regexp "[A-Za-z0-9]+@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)+"

Figura 11: Controllo con una regexp XSD

Un esempio che corrisponde a questa espressione regolare:

3.8.3.1. Considerazioni sull'uso

Si noti che le espressioni regolari XSD non supportano i soliti escape \x o \u per l'espressione esadecimale di byte o punti di codice Unicode. Tuttavia, in CDDL, le espressioni regolari XSD sono contenute in stringhe di testo, la cui notazione letterale fornisce escape \u; questo dovrebbe essere sufficiente per la maggior parte delle applicazioni che utilizzano espressioni regolari per stringhe di testo. (Si noti che questo significa anche che c'è un livello di escape delle stringhe prima che vengano applicate le regole di escape XSD.)

Le espressioni regolari XSD supportano la sottrazione di classi di caratteri, una caratteristica che spesso non si trova nelle librerie di espressioni regolari; gli scrittori di specifiche potrebbero voler usare questa caratteristica con parsimonia. Considerazioni simili si applicano alle classi di caratteri Unicode; dove queste sono usate, la specifica che impiega CDDL DOVREBBE identificare quali versioni Unicode sono indirizzate.

Altre sorprese per gli utenti non frequenti delle espressioni regolari XSD possono includere quanto segue:

  • Nessun supporto diretto per l'insensibilità alle maiuscole e minuscole. Sebbene l'insensibilità alle maiuscole e minuscole sia per lo più passata di moda nella progettazione dei protocolli, a volte è necessaria e deve quindi essere espressa manualmente come in "[Cc][Aa][Ss][Ee]".

  • Il supporto per classi di caratteri popolari come \w e \d è basato sulle proprietà dei caratteri Unicode; questo spesso non è ciò che si desidera in un protocollo basato su ASCII e può quindi portare a sorprese. (\s e \S hanno i loro significati più convenzionali, e "." corrisponde a qualsiasi carattere tranne i caratteri di fine riga \r o \n.)

3.8.3.2. Discussione

Ci sono molte varianti di espressioni regolari usate nella comunità di programmazione. Ad esempio, le Perl-Compatible Regular Expressions (PCRE) sono ampiamente usate e sono probabilmente più utili delle espressioni regolari XSD. Tuttavia, non esiste un riferimento normativo per le PCRE che potrebbe essere usato nel presente documento. Invece, optiamo per le espressioni regolari XSD per ora. C'è un precedente per questa scelta nell'IETF, ad esempio, in YANG [RFC7950].

Si noti che CDDL usa i controlli come suo principale punto di estensione. Questo crea l'opportunità di aggiungere ulteriori formati di espressioni regolari oltre a quello referenziato qui, se lo si desidera. Come esempio, una proposta per un controllo ".pcre" è definita in [CDDL-Freezer].

3.8.4. Operatori di controllo .cbor e .cborseq

Un controllo ".cbor" su una stringa di byte indica che la stringa di byte trasporta un elemento dati codificato in CBOR. Decodificato, l'elemento dati corrisponde al tipo dato come argomento di destra (type1 nel prossimo esempio).

bytes .cbor type1

Allo stesso modo, un controllo ".cborseq" su una stringa di byte indica che la stringa di byte trasporta una sequenza di elementi dati codificati in CBOR. Quando gli elementi dati sono presi come un array, l'array corrisponde al tipo dato come argomento di destra (type2 nel prossimo esempio).

bytes .cborseq type2

(La conversione della sequenza codificata in un array può essere effettuata, ad esempio, avvolgendo la stringa di byte tra i due byte 0x9f e 0xff e decodificando la stringa di byte avvolta come un elemento dati codificato in CBOR.)

3.8.5. Operatori di controllo .within e .and

Un controllo ".and" su un tipo indica che l'elemento dati corrisponde sia al tipo di sinistra che al tipo dato come destra. (Formalmente, il tipo risultante è l'intersezione dei due tipi dati.)

type1 .and type2

Una variante del controllo ".and" è il controllo ".within", che esprime un'intenzione aggiuntiva: il tipo di sinistra è inteso come un sottoinsieme del tipo di destra.

type1 .within type2

Mentre entrambe le forme hanno la semantica formale identica (intersezione), l'intento della forma ".within" è che il lato destro dia una guida ai tipi consentiti sul lato sinistro, che è tipicamente un socket (Sezione 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]

Per ".within", uno strumento potrebbe segnalare un errore se type1 consente elementi dati che non sono consentiti da type2. Al contrario, per ".and", non c'è aspettativa che type1 sia già un sottoinsieme di type2.

3.8.6. Operatori di controllo .lt, .le, .gt, .ge, .eq, .ne, e .default

I controlli .lt, .le, .gt, .ge, .eq e .ne specificano un vincolo sul tipo di sinistra affinché sia un valore minore di, minore o uguale a, maggiore di, maggiore o uguale a, uguale a, o non uguale a un valore dato come tipo di destra (contenente solo quel singolo valore). Nella presente specifica, i primi quattro controlli (.lt, .le, .gt e .ge) sono definiti solo per i tipi numerici, poiché questi hanno una relazione di ordinamento naturale.

speed = number .ge 0  ; unità: m/s

.ne e .eq sono definiti sia per valori numerici che per valori di altri tipi. Se uno dei valori non è di un tipo numerico, l'uguaglianza è determinata come segue: le stringhe di testo sono uguali (soddisfano .eq / non soddisfano .ne) se sono identiche byte per byte; lo stesso vale per le stringhe di byte. Gli array sono uguali se hanno lo stesso numero di elementi, tutti uguali a coppie nell'ordine tra gli array. Le mappe sono uguali se hanno lo stesso numero di coppie chiave/valore e c'è uguaglianza a coppie tra le coppie chiave/valore tra le due mappe. I valori etichettati sono uguali se hanno entrambi lo stesso tag e i valori sono uguali. I valori di tipi semplici corrispondono se sono gli stessi valori. I tipi numerici che si verificano all'interno di array, mappe o valori etichettati sono uguali se il loro valore numerico è uguale ed entrambi sono interi o entrambi sono valori in virgola mobile. Tutti gli altri casi non sono uguali (ad esempio, confronto di una stringa di testo con una stringa di byte).

Una variante del controllo ".ne" è il controllo ".default", che esprime un'intenzione aggiuntiva: il valore specificato dal tipo di destra è inteso come valore predefinito per il tipo di sinistra dato, e il controllo implicito .ne è lì per impedire che questo valore venga inviato sul filo. Questo controllo è significativo solo quando il tipo di controllo è usato in un contesto facoltativo; altrimenti, non ci sarebbe modo di sfruttare il valore predefinito.

timer = {
time: uint,
? displayed-step: (number .gt 0) .default 1
}

3.9. Presa/Spina (Socket/Plug)

Sia per le scelte di tipo che per le scelte di gruppo, è definito un meccanismo che facilita l'inizio con scelte vuote e il loro assemblaggio successivo, potenzialmente in file separati che vengono concatenati per costruire la specifica completa.

Per convenzione, i punti di estensione CDDL sono contrassegnati con un segno di dollaro iniziale (tipi) o due segni di dollaro iniziali (gruppi). Gli strumenti onorano tale convenzione non sollevando un errore se tale tipo o gruppo non è definito affatto; il simbolo viene quindi preso come una scelta di tipo vuota (scelta di gruppo), cioè non sono disponibili scelte.

tcp-header = {seq: uint, ack: uint, * $$tcp-option}

; più tardi, in un file diverso

$$tcp-option //= (
sack: [+(left: uint, right: uint)]
)

; e, forse in un altro file

$$tcp-option //= (
sack-permitted: true
)

I nomi che iniziano con un singolo "$" sono "prese di tipo" (type sockets), che iniziano come un tipo vuoto e sono destinati ad essere estesi tramite "/=". I nomi che iniziano con un doppio "$$" sono "prese di gruppo" (group sockets), che iniziano come una scelta di gruppo vuota e sono destinati ad essere estesi tramite "//=". In entrambi i casi, non è un errore se non c'è affatto una definizione per una presa; questo significa quindi che non c'è modo di soddisfare la regola (cioè, la scelta è vuota).

Per convenzione, tutte le definizioni (spine) per i nomi delle prese devono essere aumenti, cioè devono usare "/=" e "//=", rispettivamente.

Riprendendo l'esempio illustrato nella Figura 7, il meccanismo presa/spina potrebbe essere usato come mostrato nella Figura 12:

PersonalData = {
? displayName: tstr,
NameComponents,
? age: uint,
* $$personaldata-extensions
}

NameComponents = (
? firstName: tstr,
? familyName: tstr,
)

; Quanto sopra funziona già così com'è.
; Ma poi, possiamo aggiungere più tardi:

$$personaldata-extensions //= (
favorite-salsa: tstr,
)

; e ancora, altrove:

$$personaldata-extensions //= (
shoesize: uint,
)

Figura 12: Esempio di dati personali: Utilizzo dell'estensibilità Presa/Spina

3.10. Generici (Generics)

Usando le parentesi angolari, il lato sinistro di una regola può aggiungere parametri formali dopo il nome definito, come in:

messages = message<"reboot", "now"> / message<"sleep", 1..100>
message<t, v> = {type: t, value: v}

Quando si usa una regola generica, i parametri formali sono legati agli argomenti effettivi forniti (usando anche parentesi angolari), all'interno dell'ambito della regola generica (come se ci fosse una regola della forma parametro = argomento).

Le regole generiche possono essere usate per stabilire nomi sia per i tipi che per i gruppi.

(In questo momento, ci sono alcune limitazioni all'annidamento dei generici nello strumento CDDL descritto nell'Appendice F.)

3.11. Precedenza degli operatori

Come con qualsiasi linguaggio che ha più caratteristiche sintattiche come operatori prefissi e infissi, CDDL ha operatori che legano più strettamente di altri. Questo diventa più complicato di, diciamo, in ABNF, poiché CDDL ha sia tipi che gruppi, con operatori specifici per questi concetti. Gli operatori di tipo (come "/" per la scelta del tipo) operano sui tipi, mentre gli operatori di gruppo (come "//" per la scelta del gruppo) operano sui gruppi. I tipi possono essere semplicemente usati nei gruppi, ma i gruppi devono essere messi tra parentesi (come array o mappe) per diventare tipi. Quindi, gli operatori di tipo legano naturalmente più strettamente degli operatori di gruppo.

Per esempio, in

t = [group1]
group1 = (a / b // c / d)
a = 1 b = 2 c = 3 d = 4

group1 è una scelta di gruppo tra la scelta di tipo di a e b e la scelta di tipo di c e d. Questo diventa più rilevante una volta aggiunte le chiavi membro e/o le occorrenze:

t = {group2}
group2 = (? ab: a / b // cd: c / d)
a = 1 b = 2 c = 3 d = 4

è una scelta di gruppo tra il membro facoltativo "ab" di tipo a o b e il membro "cd" di tipo c o d. Si noti che l'opzionalità è collegata alla prima scelta ("ab"), non alla seconda scelta.

Allo stesso modo, in

t = [group3]
group3 = (+ a / b / c)
a = 1 b = 2 c = 3

group3 è una ripetizione di una scelta di tipo tra a, b e c; se solo a deve essere ripetibile, è necessaria una scelta di gruppo per focalizzare l'occorrenza:

t = [group4]
group4 = (+ a // b / c)
a = 1 b = 2 c = 3

group4 è una scelta di gruppo tra un a ripetibile e un singolo b o c.

Un commento è stato che la semantica di group3 potrebbe essere controintuitiva. In generale, come con molti altri linguaggi con regole di precedenza degli operatori, si incoraggia lo scrittore della specifica a non fare affidamento su di esse, ma a inserire parentesi liberamente per guidare i lettori che non hanno familiarità con le regole di precedenza di CDDL:

t = [group4a]
group4a = ((+ a) // (b / c))
a = 1 b = 2 c = 3

Le precedenze degli operatori, in sequenza da legame lasco a stretto, sono definite nell'Appendice B e riassunte nella Tabella 1. (Le arità date sono 1 per operatori prefissi unari e 2 per operatori infissi binari.)

OperatoreAritàOpera suPrecedenza
=2nome = tipo, nome = gruppo1
/=2nome /= tipo1
//=2nome //= gruppo1
//2gruppo // gruppo2
,2gruppo, gruppo3
*1* gruppo4
n*m1n*m gruppo4
+1+ gruppo4
?1? gruppo4
=>2tipo => tipo5
:2nome: tipo5
/2tipo / tipo6
..2tipo..tipo7
...2tipo...tipo7
.ctrl2tipo .ctrl tipo7
&1&gruppo8
~1~tipo8

Tabella 1: Riepilogo delle precedenze degli operatori