2. データ構造仕様のスタイル
CDDLは、JSONによって開拓され、現在CBORで洗練されているデータモデルを採用しているコミュニティで使用されている仕様のスタイルに焦点を当てています。
CBORデータモデルには、数値、単純値(false、true、nil)、テキスト文字列、バイト文字列など、多かれ少なかれアトミックな要素がいくつかあります。CDDLは、それらの構造を指定することに焦点を当てていません。もちろん、CDDLではデータ項目にCBORタグを追加することもできます。
これらのアトミック要素を超えて、データ構造定義言語のさらなる構成要素は、合成に使用されるデータ型です。CBORでは配列とマップ(JSONでは「配列」と「オブジェクト」と呼ばれます)です。これらは2つの表現形式にすぎませんが、4つの緩やかに区別できる合成スタイルを指定するために使用されます。
-
ベクトル (Vector): ほとんどが同じ意味を持つ要素の配列。署名付きデータ項目に関連付けられた署名のセットは、ベクトルの典型的なアプリケーションです。
-
レコード (Record): データ構造定義で詳述されているように、異なる位置的に定義された意味を持つ要素の配列。x座標(最初に来る)とy座標(2番目に来る)の配列として指定された2Dポイントは、CBOR小数部の指数(最初)と仮数(2番目)のペアと同様に、レコードの例です。
-
テーブル (Table): マップキーのドメインからマップ値のドメインへのマップであり、ほとんどが同じ意味を持ちます。それぞれがその特定の言語に翻訳されたテキスト文字列にマップされた言語タグのセットは、テーブルの例です。キードメインは通常、仕様によって特定のセットに制限されず、アプリケーションに対して開かれています。たとえば、IPアドレスをメディアアクセス制御(MAC)アドレスにマッピングするテーブルでは、仕様はすべての可能なIPアドレスを予測しようとはしません。JavaScriptのような言語では、キードメインの一般性を実現するために、プレーンな「オブジェクト」ではなく「Map」がよく採用されます。
-
構造体 (Struct): 仕様で定義されたマップキーのドメインから、それぞれの意味が特定のマップキーにバインドされているマップ値のドメインへのマップ。これは、JSONオブジェクトについて考えるときに多くの人が頭に浮かぶものです。CBORには、テキスト文字列だけでなくマップキーを使用する機能が追加されています。構造体は、レコードが使用されるのと同様の問題を解決するために使用できます。明示的なマップキーの使用により、オプション性と拡張性が容易になります。
2つの重要な概念がCDDLの基盤を提供します。
-
CDDLですべての4つのタイプの合成を個別に定義したり、配列(ベクトルとレコード)用とマップ(テーブルと構造体)用にそれぞれ1種類を定義したりする代わりに、CDDLには1種類の合成しかありません。それがグループ(2.1項)です。
-
もう1つの重要な概念は型の概念です。CDDL仕様全体が型(最初のルールによって定義されるもの)を定義しており、これは正式には、この仕様の「インスタンス」として受け入れられるCBORデータ項目のセットです。CDDLは、「uint」(符号なし整数)や「tstr」(テキスト文字列)などのいくつかの基本的な型を事前定義しており、CBORデータ項目の単純な形式表記を頻繁に使用しています。CBORデータ項目として表現できる各値は、それ自体が型でもあります(例:「1」)。型は他の型の選択として構築できます。たとえば、「int」は「uint」または「nint」(負の整数)のいずれかです。最後に、型はグループからの配列またはマップとして構築できます。
このセクションの残りの部分では、CDDLのいくつかの基本的な概念を紹介し、セクション3では追加の構文を定義します。付録Cは、CDDLのセマンティクスの簡潔な要約を提供します。
2.1. CDDLにおけるグループと合成
CDDLグループは、グループエントリのリストであり、それぞれが名前/値ペアまたはより複雑なグループ式(これは名前/値ペアのシーケンスを表します)になることができます。CDDLグループは、特定の名前/値ペアのシーケンスには一致するが、他のものには一致しない文法の生成規則です。この文法は、解析表現文法(PEG)の概念に基づいています(付録Aを参照)。
配列コンテキストでは、名前/値ペアの値のみが表現されます。名前は注釈のみであり(必要ない場合はグループ仕様から除外できます)。マップコンテキストでは、名前はマップキー(「メンバーキー」)になります。
配列コンテキストでは、グループ内の要素の実際の順序が重要です。その順序は、実際の配列要素をグループ内のエントリに関連付けることを可能にする情報であるためです。マップコンテキストでは、グループ内のエントリの順序は関係ありません(ただし、グループエントリを順序どおりに書き留める必要は依然としてあります)。
配列は、グループが名前/値ペアのシーケンスと一致し、その値部分が配列の要素と順序どおりに正確に一致する場合、グループとして指定された仕様と一致します。
マップは、グループが名前/値ペアのシーケンスと一致し、これらの名前/値ペアのすべてがマップに存在し、マップにグループでカバーされていない名前/値ペアがない場合、グループとして指定された仕様と一致します。
マップ定義でグループを直接使用する簡単な例は次のとおりです。
person = {
age: int,
name: tstr,
employer: tstr,
}
図 1: マップでのグループの直接使用
グループの3つのエントリは、マップを作成する中括弧の間に記述されます。ここでは、「age」、「name」、「employer」がマップキーのテキスト文字列になる名前であり、「int」と「tstr」(テキスト文字列)がこれらのキーの下にあるマップ値の型です。
グループ自体は(その周りにマップを作成せずに)(丸)括弧に入れて、ルールで使用することで名前を付けることができます。
pii = (
age: int,
name: tstr,
employer: tstr,
)
図 2: 基本的なグループ
この個別の名前付きグループ定義により、図1を次のように言い換えることができます。
person = {
pii
}
図 3: 名前によるグループの使用
(中)括弧はマップの作成を意味することに注意してください。グループ自体は、マップで使用されるか配列で使用されるかについては中立です。
図1に示すように、他のブラケットのセットが存在する場合、グループの括弧はオプションです。これらは引き続き使用できることに注意してください。これはあまり現実的ではありませんが、完全に有効な例につながります。
person = {(
age: int,
name: tstr,
employer: tstr,
)}
図 4: マップでの括弧付きグループの使用
グループを使用して構造体の共通部分を因数分解できます。たとえば、図5のようにコピー/貼り付けスタイルで仕様を作成する代わりに、共通のサブグループを因数分解し、名前を選択して、特定のパーツのみを個々のマップに書き込むことができます(図6)。
person = {
age: int,
name: tstr,
employer: tstr,
}
dog = {
age: int,
name: tstr,
leash-length: float,
}
図 5: コピー/貼り付けによるマップ
person = {
identity,
employer: tstr,
}
dog = {
identity,
leash-length: float,
}
identity = (
age: int,
name: tstr,
)
図 6: 因数分解のためのグループの使用
上記の定義の中括弧内のリストは(匿名の)グループを構成し、「identity」は名前付きグループであり、他のグループの一部として含めることができることに注意してください(例のように未定義であるか、それ自体が名前付きであるかに関係なく)。
2.1.1. 使用法
グループは、CDDLでデータ構造を構成するために使用される手段です。これらの構造を定義する際に、コンテキスト内で(匿名で)グループを定義するか、別のルールで定義してそれぞれの名前で(場合によっては複数回)参照するかは、スタイルの問題です。
これにより、データ構造のすべての小さな部分を定義し、それらを使用してより大きなプロトコルデータユニットを構成するか、必要な場所ですべての定義をアドホックに持つ1つの大きなプロトコルデータユニットのみを持つことができます。
2.1.2. 構文
構成構文は、簡潔で読みやすいことを目的としています。
-
グループの開始と終了は、「(」と「)」でマークできます。
-
グループ内のエントリの定義は次のように記述されます:keytype => valuetype,(「keytypeはvaluetypeにマップされる」と読みます)。コンマは実際にはオプションですが(最後のエントリだけでなく)、設定することをお勧めします。テキスト文字列または整数リテラルをキーとして直接使用する一般的なケースでは、二重矢印をコロンに置き換えることができます(3.5.1項を参照)。これは、ドキュメントのためだけに配列の要素に名前を付ける一般的な方法でもあります(3.4項を参照)。
基本的なエントリは、keytypeとvaluetypeで構成され、どちらも型です(2.2項)。このエントリは、名前がkeytypeにあり、値がvaluetypeにある任意の名/値ペアと一致します。
グループエントリのシーケンスとして定義されたグループは、エントリが一致する順序で連結によって構成される名前/値ペアのシーケンスと一致します。
グループ定義には、グループ間の選択を含めることもできます(2.2.2項を参照)。
2.2. 型 (Types)
2.2.1. 値 (Values)
数値や文字列などの値は、型の代わりに使用できます。(たとえば、これはキーの型に対して行うのが非常に一般的なことであり、CDDLがこれに追加の便利な構文を提供するほど一般的です。)
値の表記法はC言語に基づいていますが、すべての構文バリエーションを提供するわけではありません(詳細は付録Bを参照)。数値の値表記は、整数値(小数部や指数が指定されていない -- NR1 [ISO6093]; "NR"は"numerical representation"の略)と浮動小数点値(小数部、指数、またはその両方が存在する -- NR2またはNR3)の区別をCから継承しているため、型 "1" には浮動小数点数は含まれませんが、型 "1e3" と "1.5" はどちらも浮動小数点数であり、整数は含まれません。
2.2.2. 選択 (Choices)
型を許可する多くの場所では、"/"(スラッシュ)で区切られた型間の選択も許可されます。構築を明確にする必要がある場合は、選択構成全体を括弧に入れることができます(CDDL文法の詳細については付録Bを参照してください)。
値の選択を使用して、列挙を表現できます。
attire = "bow tie" / "necktie" / "Internet attire"
protocol = 6 / 17
型と同様に、CDDLでは "//"(二重スラッシュ)で区切られたグループ間の選択も許可されます。"//" 演算子は他のCDDL演算子よりもはるかに弱く結合することに注意してください。そのため、次の例の "delivery" 内の各行は、グループ選択における独自の代替手段です。
address = { delivery }
delivery = (
street: tstr, ? number: uint, city //
po-box: uint, city //
per-pickup: true
)
city = (
name: tstr, zip-code: uint
)
グループ選択は、選択肢内の代替手段が可能な名前/値ペアシーケンスのセットの和集合と一致します。
型の選択とグループの選択の両方について、"=" の代わりにそれぞれ "/=" と "//=" を使用することで、後で別のルールでルールに追加の代替手段を追加できます。
attire /= "swimwear"
delivery //= (
lat: float, long: float, drone-type: tstr
)
名前が最初に "/=" または "//=" で使用されてもエラーではありません("=" で「作成」する必要はありません)。
2.2.2.1. 範囲 (Ranges)
選択を構成するすべての値を名前付けする代わりに、CDDLでは、順序関係にある2つの値(下限(最初の値)と上限(2番目の値))から範囲を作成できます。範囲は、指定された両方の境界を含む(2つの値を ".." で結合することで示されます)か、下限を含み上限を除外する(代わりに "..." を使用することで示されます)ことができます。下限が上限を超える場合、結果の型は空集合になります(この動作は、ジェネリクス(3.10項)が使用されている場合に望ましい場合があります)。
device-address = byte
max-byte = 255
byte = 0..max-byte ; 包括的範囲
first-non-byte = 256
byte1 = 0...first-non-byte ; byte1はbyteと同等
CDDLは現在、整数間(整数値に一致)または浮動小数点値間(浮動小数点値に一致)の範囲のみを許可しています。型で両方が必要な場合は、2種類の範囲間の型選択を(不格好に)使用できます。
int-range = 0..10 ; 整数のみ一致
float-range = 0.0..10.0 ; floatのみ一致
BAD-range1 = 0..10.0 ; 未定義
BAD-range2 = 0.0..10 ; 未定義
numeric-range = int-range / float-range
(3.8.6項の制御演算子 .lt/.ge および .le/.gt も参照してください。)
CDDLではドットは有効な名前継続文字であることに注意してください。したがって、
min..max
は範囲式ではなく単一の名前です。範囲演算子の左側として名前を使用する場合は、次のようにスペースを使用します。
min .. max
範囲演算子を分離するためです。
2.2.2.2. グループを選択に変換する
一部の選択は、多数の値(多くの場合整数)から構築され、それぞれの値には仕様で意味的な名前を付けるのが最適です。これらの整数のおのおのに名前を付けてから選択に蓄積する代わりに、CDDLでは、グループの前に "&" 文字を付けることで、グループから選択を構築できます。
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,
)
配列でのグループの使用(3.4項)と同様に、メンバー名には文書的な価値しかありません(特に、その選択から取得された整数を表示するときにツールによって使用される場合があります)。
2.2.3. 表現型 (Representation Types)
CDDLでは、CBOR表現(具体的には、メジャータイプと追加情報。[RFC7049]の第2節を参照)を参照してデータ項目タイプを指定できます。これがどのように使用されるかは、プレリュード(付録D)から明らかであるはずです。ハッシュマーク("#")の後にオプションでメジャータイプを識別する0から7までの数字が続き、その後にドットと追加情報を指定する数字が続くことができます。この構築は、CBORでシリアル化できる値のセット(つまり "any")、指定されている場合は指定されたメジャータイプ、または両方が指定されている場合は追加情報付きの指定されたメジャータイプを指定します。メジャータイプ6(タグ)が使用される場合、タグ付きアイテムのタイプは、括弧内に追加することで指定できます。
この表記はCBORシリアル化に基づいているものの、データモデルレベルでの値のセットに関するものであることに注意してください。たとえば、"#7.25" は半精度浮動小数点数として表現できる値のセットを指定します。これらの値も半精度浮動小数点数としてシリアル化する必要があることを義務付けるものではありません。CDDLは、シリアル化バリアントの選択を制限する言語手段を提供しません。これにより、同じ値(の一部)をシリアル化するための根本的に異なる方法を使用するJSONでCDDLを使用することも可能になります。
プレリュードの外部で表現型を利用する必要がある場合があります。たとえば、仕様は既存のタグをより具体的な方法で利用することから始めるか、プレリュードで定義されていない新しいタグを定義することができます。
my_breakfast = #6.55799(breakfast) ; cbor-anyは一般的すぎます!
breakfast = cereal / porridge
cereal = #6.998(tstr)
porridge = #6.999([liquid, solid])
liquid = milk / water
milk = 0
water = 1
solid = tstr
2.2.4. ルート型 (Root Type)
CDDLデータ構造定義のルートを識別するための特別な構文はありません。その役割は単に、ファイルで定義された最初のルールによって果たされます。
これは、データ構造を定義するための通常のトップダウンアプローチ、つまり大きなデータ構造ユニットをより小さな部分に分解することによって動機付けられています。ただし、ルートタイプを除いて、この順序に厳密に従う必要はありません。
(グループをルートとして使用する方法はないことに注意してください -- 型である必要があります。)