Passa al contenuto principale

3. Presentation Language (Linguaggio di presentazione)

Questo documento tratta la formattazione dei dati in una rappresentazione esterna. Verrà utilizzata la seguente sintassi di presentazione molto basica e definita in modo piuttosto informale.

3.1 Basic Block Size (Dimensione del blocco di base)

La rappresentazione di tutti gli elementi di dati è specificata esplicitamente. La dimensione del blocco di dati di base è un byte (ovvero 8 bit). Gli elementi di dati multi-byte sono concatenazioni di byte, da sinistra a destra, dall'alto verso il basso. Dal flusso di byte, un elemento multi-byte (un numero nell'esempio seguente) viene formato (utilizzando la notazione C) come segue:

value = (byte[0] << 8*(n-1)) | (byte[1] << 8*(n-2)) |
... | byte[n-1];

Questo ordinamento dei byte per i valori multi-byte è il comune ordinamento dei byte di rete o formato big-endian.

3.2 Miscellaneous (Varie)

I commenti iniziano con /* e terminano con */.

I componenti opzionali sono indicati racchiudendoli in [[ ]] (doppie parentesi quadre).

Le entità di un singolo byte contenenti dati non interpretati sono di tipo opaque.

Un alias di tipo T' per un tipo esistente T è definito da:

T T';

3.3 Numbers (Numeri)

Il tipo di dati numerici di base è un byte senza segno (uint8). Tutti i tipi di dati numerici più grandi sono formati da serie di byte di lunghezza fissa concatenati come descritto nella Sezione 3.1 e sono anch'essi senza segno. I seguenti tipi numerici sono predefiniti:

uint8 uint16[2];
uint8 uint24[3];
uint8 uint32[4];
uint8 uint64[8];

Tutti i valori, qui e altrove nella specifica, sono trasmessi nell'ordine dei byte di rete (big-endian); l'uint32 rappresentato dai byte esadecimali 01 02 03 04 è equivalente al valore decimale 16909060.

3.4 Vectors (Vettori)

Un vettore (array unidimensionale) è un flusso di elementi di dati omogenei. La dimensione del vettore può essere specificata al momento della documentazione o lasciata non specificata fino al runtime. In entrambi i casi, la lunghezza dichiara il numero di byte, non il numero di elementi, nel vettore. La sintassi per specificare un nuovo tipo, T', che è un vettore di lunghezza fissa di tipo T è:

T T'[n];

Qui, T' occupa n byte nel flusso di dati, dove n è un multiplo della dimensione di T. La lunghezza del vettore non è inclusa nel flusso codificato.

Nell'esempio seguente, Datum è definito come tre byte consecutivi che il protocollo non interpreta, mentre Data è tre Datum consecutivi, consumando un totale di nove byte.

opaque Datum[3];      /* tre byte non interpretati */
Datum Data[9]; /* tre vettori consecutivi di 3 byte */

I vettori a lunghezza variabile sono definiti specificando un sottointervallo di lunghezze legali, inclusivamente, utilizzando la notazione <floor..ceiling>. Quando questi sono codificati, la lunghezza effettiva precede il contenuto del vettore nel flusso di byte. La lunghezza sarà nella forma di un numero che consuma tanti byte quanti necessari per contenere la lunghezza massima specificata del vettore (ceiling). Un vettore a lunghezza variabile con un campo di lunghezza effettiva pari a zero è chiamato vettore vuoto.

T T'<floor..ceiling>;

Nell'esempio seguente, mandatory è un vettore che deve contenere tra 300 e 400 byte di tipo opaque. Non può mai essere vuoto. Il campo della lunghezza effettiva consuma due byte, un uint16, che è sufficiente per rappresentare il valore 400 (vedere Sezione 3.3). Allo stesso modo, longer può rappresentare fino a 800 byte di dati, o 400 elementi uint16, e può essere vuoto. La sua codifica includerà un campo di lunghezza effettiva di due byte anteposto al vettore. La lunghezza di un vettore codificato deve essere un multiplo pari della lunghezza di un singolo elemento (ad esempio, un vettore di 17 byte di uint16 sarebbe illegale).

opaque mandatory<300..400>;
/* il campo di lunghezza è di due byte, non può essere vuoto */
uint16 longer<0..800>;
/* da zero a 400 interi senza segno a 16 bit */

3.5 Enumerateds (Enumerazioni)

È disponibile un tipo di dati sparso aggiuntivo, chiamato enum. Ogni definizione è un tipo diverso. Solo le enumerazioni dello stesso tipo possono essere assegnate o confrontate. A ogni elemento di un'enumerazione deve essere assegnato un valore, come dimostrato nell'esempio seguente. Poiché gli elementi dell'enumerazione non sono ordinati, possono essere assegnati qualsiasi valore unico, in qualsiasi ordine.

enum { e1(v1), e2(v2), ... , en(vn) [[, (n)]] } Te;

Le estensioni future o le aggiunte al protocollo possono definire nuovi valori. Le implementazioni devono essere in grado di analizzare e ignorare valori sconosciuti a meno che la definizione del campo non indichi diversamente.

Un'enumerazione occupa tanto spazio nel flusso di byte quanto il suo valore ordinale massimo definito. La seguente definizione causerebbe l'uso di un byte per trasportare campi di tipo Color:

enum { red(3), blue(5), white(7) } Color;

Si può opzionalmente specificare un valore senza il suo tag associato per forzare la definizione della larghezza senza definire un elemento superfluo.

Nell'esempio seguente, Taste consumerà due byte nel flusso di dati ma può assumere solo i valori 1, 2 o 4 nella versione corrente del protocollo.

enum { sweet(1), sour(2), bitter(4), (32000) } Taste;

I nomi degli elementi di un'enumerazione hanno un ambito limitato al tipo definito. Nel primo esempio, un riferimento completamente qualificato al secondo elemento dell'enumerazione sarebbe Color.blue. Tale qualificazione non è richiesta se il target dell'assegnazione è ben specificato.

Color color = Color.blue;     /* sovraspecificato, legale */
Color color = blue; /* corretto, tipo implicito */

I nomi degli elementi non devono essere unici. Il valore numerico tra gli elementi deve essere unico, tuttavia.

enum { low(1), medium(2), high(2) } Priority;  /* SBAGLIATO */

Per le enumerazioni che non vengono mai convertite in rappresentazione esterna, l'informazione numerica può essere omessa.

enum { low, medium, high } Priority;

3.6 Constructed Types (Tipi costruiti)

I tipi struttura possono essere costruiti da tipi primitivi per comodità. Ogni specifica dichiara un nuovo tipo unico. La sintassi per la definizione è molto simile a quella di C:

struct {
T1 f1;
T2 f2;
...
Tn fn;
} T;

I campi vettoriali a lunghezza fissa e variabile sono consentiti utilizzando la sintassi vettoriale standard. Le strutture V1 e V2 nell'esempio delle varianti (Sezione 3.8) lo dimostrano.

I campi all'interno di una struttura possono essere qualificati utilizzando il nome del tipo, con una sintassi molto simile a quella disponibile per le enumerazioni. Ad esempio, T.f2 si riferisce al secondo campo della dichiarazione precedente.

3.7 Constants (Costanti)

Ai campi e alle variabili può essere assegnato un valore fisso utilizzando =, come in:

struct {
T1 f1 = 8; /* T.f1 deve sempre essere 8 */
T2 f2;
} T;

3.8 Variants (Varianti)

Le strutture definite possono avere varianti basate su qualche conoscenza disponibile nell'ambiente. Il selettore deve essere un tipo enumerato che definisce le possibili varianti definite dalla struttura. Ogni ramo della struttura variante specifica il tipo del campo di quella variante e un'etichetta di campo opzionale. Il meccanismo attraverso il quale la variante viene selezionata a runtime non è prescritto dal linguaggio di presentazione.

struct {
T1 f1;
T2 f2;
....
Tn fn;
select (E) {
case e1: Te1 [[fe1]];
case e2: Te2 [[fe2]];
....
case en: Ten [[fen]];
};
} Tv;

Ad esempio:

enum { apple, orange, banana } VariantTag;

struct {
uint16 number;
opaque string<0..10>; /* lunghezza variabile */
} V1;

struct {
uint32 number;
opaque string[10]; /* lunghezza fissa */
} V2;

struct {
VariantTag type;
select (VariantRecord.type) {
case apple: V1;
case orange:
case banana: V2;
};
} VariantRecord;