メインコンテンツまでスキップ

3. 構文

本節では、CDDLの全体的な構文を、構文を説明するためのいくつかの例とともに示します。(定義は過度に形式的であることを意図していません。詳細は付録Bを参照してください。)

3.1. 一般的な規則

基本的な構文はABNF [RFC5234]に着想を得ており、以下の特徴があります。

  • グループを定義するか型を定義するかにかかわらず、ルールは名前で定義され、等号 "=" が続き、その定義のそれぞれの構文規則に従った実際の定義が続きます。

  • 名前は、セット {"A" から "Z", "a" から "z", "0" から "9", "", "-", "@", ".", "$"} の任意の文字で構成でき、アルファベット文字("@", "", "$" を含む)で始まり、そのような文字または数字で終わります。

    • 名前は大文字と小文字を区別します。
    • 名前は小文字で始めることが推奨されるスタイルです。
    • ハイフンはアンダースコアよりも好まれます(「ベアワード」(3.5.1項)で、セマンティクスが実際にアンダースコアを必要とする場合を除きます)。
    • ピリオドは、大きな仕様書でモジュール構造を表現するのに役立つ場合があります("tcp.throughput" 対 "udp.throughput" など)。
    • 付録Dに記載されているように、CDDLプレリュードでいくつかの名前が事前に定義されています。
    • ルール名(型またはグループ)は実際のCBORエンコーディングには表示されませんが、メンバーキーで「ベアワード」として使用される名前は表示されます。
  • コメントは ";"(セミコロン)文字で始まり、行末(LFまたはCRLF)で終わります。

  • 文字列内を除き、空白(スペース、改行、およびコメント)は読みやすさのために構文要素を区切るために使用されます(また、相互に続く識別子、範囲演算子、または数値を区切るためにも使用されます)。それ以外の場合は完全にオプションです。

  • 16進数は接頭辞 "0x"(引用符なし)が付き、大文字と小文字を区別しません。同様に、2進数は接頭辞 "0b" が付きます。

  • テキスト文字列は二重引用符 '"' 文字で囲まれます。これらは[RFC8259]の第7節で定義されている文字列の規則に従います。(ABNFユーザーは、CDDLではテキスト文字列の大文字と小文字を区別しないという概念がサポートされていないことに注意してください。必要に応じて、正規表現を使用できます(3.8.3項)。)

  • バイト文字列は単一引用符 "'" 文字で囲まれ、"h" または "b64" の接頭辞が付く場合があります。接頭辞がない場合、文字列はテキスト文字列と同様に解釈されますが、単一引用符はエスケープする必要があり、結果のUTF-8バイトはバイト文字列(メジャータイプ2)としてマークされます。"h" または "b64" の接頭辞が付く場合、文字列はそれぞれ16進数のペアのシーケンス(base16; [RFC4648]の第8節を参照)またはbase64(url)文字列([RFC4648]の第4節または第5節)([RFC7049]の第6節の診断表記と同様; 付録G.2を参照)として解釈されます。接頭辞が付く場合、文字列内に存在する空白(コメントを含む)は無視されます。

  • CDDLはそのエンコーディングにUTF-8 [RFC3629]を使用します。CDDLの処理にはUnicode正規化プロセスは含まれません。

例:

; これはコメントです
person = { g }

g = (
"name": tstr,
age: int, ; "age" はベアワードです
)

3.2. 出現 (Occurrence)

オプションの 出現 インジケーターをグループエントリの前に指定できます。これは、(1) 文字 "?"(オプション)、""(0回以上)、"+"(1回以上)のいずれか、または (2) nm の形式です。ここで、n と m はオプションの符号なし整数であり、n は下限(デフォルトは 0)、m は出現の上限(デフォルトは制限なし)です。

出現インジケーターが指定されていない場合、グループエントリは正確に1回出現することになります(1*1 が指定されたかのように)。出現インジケーターを持つグループエントリは、基本グループエントリが一致するシーケンスの数を連結することによって構成される名前/値ペアのシーケンスと一致します。ここで、その数は出現インジケーターによって許可される必要があります。

定義される可能性のあるディレクティブ/注釈以外では、CDDLは配列またはマップが定長エンコーディングを使用するか不定長エンコーディングを使用するかについて規定していないことに注意してください。つまり、仕様で配列のサイズを「オープン」にしておくことと、それが定長または不定長で交換されるという事実との間に相関関係はありません。

また、CDDLはターゲット表現のデータモデルが持っていない柔軟性を記述できることにも注意してください。これはJSONではかなり明白ですが、CBORにも関連しています。

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

前の仕様は、CBORがキー "bedroom" を複数回使用できるように変更されたことを意味するものではありません。つまり、データモデルによって課された制限により、3行目は実質的に次のようになります。

? bedroom: size,

(1回を超える出現インジケーターは、さまざまなキーを許可するグループのマップでは依然として有用です。)

3.3. 事前定義された型名

CDDLはいくつかの名前を事前に定義しています。このサブセクションではこれらの名前を要約しますが、正確な定義については付録Dを参照してください。

プリミティブデータ型の以下のキーワードが定義されています。

"bool": ブール値(メジャータイプ7、追加情報20または21)。

"uint": 符号なし整数(メジャータイプ0)。

"nint": 負の整数(メジャータイプ1)。

"int": 符号なし整数または負の整数。

"float16": 半精度浮動小数点数として表現可能な数値 [IEEE754](メジャータイプ7、追加情報25)。

"float32": 単精度浮動小数点数として表現可能な数値 [IEEE754](メジャータイプ7、追加情報26)。

"float64": 倍精度浮動小数点数として表現可能な数値 [IEEE754](メジャータイプ7、追加情報27)。

"float": float16、float32、またはfloat64のいずれか。

"bstr" または "bytes": バイト文字列(メジャータイプ2)。

"tstr" または "text": テキスト文字列(メジャータイプ3)。

(配列またはマップの事前定義された名前はないことに注意してください。これらは以下に示す構文で定義されます。)

さらに、"tdate"、"bigint"、"regexp" など、CBORタグに関連付けられたいくつかの型がプレリュードで定義されています。

3.4. 配列

配列の定義は、グループを角括弧で囲みます。

各エントリについて、3.2項で指定された出現インジケーターが許可されます。

例:

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

グループ "person" は、配列内でそれを繰り返すたびに名前と年齢が交互に生成されるように定義されています。したがって、以下は型 "unlimited-people" のデータ項目の4つの有効な値です。

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

3.5. マップ (Maps)

マップを指定するための構文は、CDDLを採用する多くの仕様の焦点となる可能性が高いため、いくつかの最適化や利便性と同様に、特別な注意に値します。構文はマップの構造体(struct)とテーブル(table)の使用法を厳密に区別していませんが、それぞれの使用法に具体的に対応しています。

しかしその前に、CBORがJSONから継承した機能、つまりCBORマップ内のキー/値ペアには固定された順序がないことを繰り返し述べておきましょう。(順序を固定することが役立つ状況を想像することはできます。たとえば、デコーダーが整数キー1、3、7に関連する値を探すことができます。順序が固定されており、デコーダーがキー3に遭遇せずにキー4に遭遇した場合、より複雑な記録管理を行うことなくキー3が利用できないと結論付けることができます。残念ながら、JSONもCBORもこれをサポートしていないため、CDDLでもこれをサポートする試みは行われませんでした。)

3.5.1. 構造体 (Structs)

マップの「構造体」としての使用法は、多くのJSONアプリケーションでJSONオブジェクトが使用される方法と似ています。

マップは、角括弧 "[]" の代わりに波括弧 "{}" を使用することを除いて、配列を定義するのと同じ方法で定義されます(3.4項を参照)。

各グループエントリに対して、3.2項で指定された出現インジケーターが許可されます。

以下は、構造が埋め込まれたレコードの例です。

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

GpsCoordinates = {
longitude : uint, ; 度、10^7倍
latitude : uint, ; 度、10^7倍
}

エンコード時、Geographyレコードは2つのメンバーを持つCBOR配列を使用してエンコードされ(グループエントリのキーは無視されます)、GpsCoordinates構造体は2つのキー/値ペアを持つCBORマップとしてエンコードされます。

構造体で使用される型は、別のルールで定義することも、その場(選択肢の場合などは括弧内に配置される可能性があります)で定義することもできます。例えば:

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

ここで、"located-samples" は構造体を参照するときに使用されるデータ型であり、"sample-point" と "samples" は使用されるキーです。これは実際には完全な例です。コロンが続く識別子は、メンバーキーのテキスト文字列(「ベアワード」メンバーキーと呼びます)として直接使用でき、二重引用符で囲まれた文字列や数値も使用できます。(他の型、特に複数の値を含む型がキーの型として使用される場合、それらの後には二重矢印が続きます。下記を参照。)

テキスト文字列キーが識別子の構文と一致しない場合(または指定者が単に二重引用符を使用することを好む場合)、テキスト文字列構文をメンバーキーの位置で使用し、その後にコロンを続けることもできます。したがって、上記の例は、メンバーキーの位置に引用符付き文字列を使用して記述することもできました。

より一般的には、上記のケースにリストされている方法以外の方法で指定された型は、二重矢印を続けることでキー型の位置で使用できます。特に、型が識別子によって名前が付けられている場合(コロンが続くと「ベアワード」として解釈され、テキスト文字列に変わるため)、二重矢印が必要です。リテラルテキスト文字列も型(単一の値、つまり指定された文字列のみを含む)を生じさせるため、この例の別の形式は次のとおりです。

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

ここで説明したコロン (":") のショートカットがどのようにして暗黙の意味を追加するかについては、以下の3.5.4項を参照してください。

二重矢印の使用法を示すより良い方法は次のとおりです。

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

以下の例は、オプションのエントリを持つ構造体を定義しています:表示名(テキスト文字列として)、名前コンポーネントの名と姓(テキスト文字列として)、および年齢情報(符号なし整数として)。

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

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

NameComponentsのグループ定義は別のマップを生成するのではなく、4つのキーすべてがPersonalDataによって構築された構造体に直接配置されることに注意してください。

この例では、CDDLの観点からはすべてのキー/値ペアがオプションです。出現インジケーターがない場合、エントリは必須です。

現在の仕様で指定されていないエントリを追加したい場合は、この可能性を明示的に追加できます。

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

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

図 7: 個人データ:拡張性の例

付録Fで説明されているCDDLツールは、この仕様の許容されるインスタンスの1つとして以下を生成しました。

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

(拡張ポイントを明示的に識別する1つの方法については、3.9項を参照してください。)

3.5.2. テーブル (Tables)

テーブルは、キーの型が単一の値以上を許可するエントリを持つマップを定義することによって指定できます。例えば:

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

ここで、各キー/値ペアのキーはデータ型x(intと定義)を持ち、値はデータ型y(floatと定義)を持ちます。

仕様がxまたはyのいずれかを制限する必要がない場合(つまり、アプリケーションがエントリごとに自由に選択できる場合)、事前定義された名前 "any" に置き換えることができます。

別の例として、以下は整数または浮動小数点数から文字列に変換する変換テーブルとして使用できます。

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

3.5.3. 非決定論的順序

配列の照合方法は完全にPEG形式論(付録Aを参照)によって決定されますが、マップは固有の順序を持たないため、マップの照合はより複雑です。PEGアルゴリズムが試行する候補となる名前/値ペアごとに、一致するメンバーがマップ全体から選択されます。特定のグループ式では、マップ内の複数のメンバーが一致する場合があります。多くの場合、グループ式はすべての一致を消費する傾向があるため、これは重要ではありません。

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

ここで、キー "fritz" を持つメンバーが存在する場合、これはグループの最初のエントリによって選択されます。残りのすべてのテキスト/数値メンバーは2番目のエントリによって選択されます(未選択のものが残っている場合、マップは一致しません)。

ただし、実際に何が選択されるかは不確定ですが、それが重要になるグループ式を作成することは可能です。

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

この式が "{3: 5, 4: 6}" に対して照合される場合、最初のグループエントリは "3: 5" を選択し、2番目のエントリの照合用に "4: 6" を残す可能性があります。あるいは、"4: 6" を選択し、2番目のエントリに何も残さない可能性もあります。この病的な非決定論は、「より一般的」なものを「より具体的」なものの前に指定すること、および一致する可能性のあるマップキー/値ペアのサブセットのみを消費する一般的なルールを持つことによって引き起こされます。これらはいずれも、マップの実際の仕様では発生しない傾向があります。執筆時点では、CDDLツールはそのようなケースを自動的に検出できません。現在のバージョンのCDDL仕様では、仕様作成者は病的に非決定論的な仕様を書かないように強く求められています。

(賢明な読者は、Standard Generalized Markup Language (SGML) で「あいまいなコンテンツモデル」、XMLで「非決定論的コンテンツモデル」と呼ばれていたものを思い出すでしょう。ここで説明されている問題はそれに関連していますが、ここでの問題は特にマップ内の順序の欠如によって引き起こされており、XMLスキーマ言語が対処する必要のないものです。RELAX NGの "interleave" パターンは仕様側で順序の欠如を明示的に処理しますが、XMLのインスタンスには常に決定論的な順序があることに注意してください。)

3.5.4. マップ内のカット (Cuts)

上記で説明した構造体の拡張性のイディオムには1つの問題があります。

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

この例では、オプションのキー "optional-key" が1つあり、存在する場合は整数にマップされます。将来の追加のためのワイルドカードもあります。

残念ながら、データ項目

{ "optional-key": "nonsense" }

はこの仕様と一致します。グループの最初のエントリは一致しませんが、2番目のエントリ(ワイルドカード)は一致します。これは非常に望ましい場合もありますが(たとえば、将来の拡張で "optional-key" の型を拡張できるようにする場合)、多くの場合そうではありません。

「カット」と呼ばれるより一般的な潜在的な機能を予測して、CDDLではマップエントリの定義にカット "^" を挿入することを許可しています。

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

この位置でのカットは、メンバーキーがカットを持つエントリの名前部分と一致すると、マップのグループ内の後のエントリで発生するそのメンバーのキーの他の潜在的な一致が許可されなくなることを意味します。言い換えると、グループエントリが一致するキーのみに基づいてキー/値ペアを選択する場合、選択を「ロックイン」します。値が一致するかどうかに関係なくこのルールが適用されるため、値が一致しない場合、マップ全体が一致に失敗します。要約すると、上記の例は、カットによって変更された仕様とは一致しなくなります。

この種の排他的なマッチングに対する要望は非常に頻繁であるため、":" ショートカット实际上はカットのセマンティクスを含むように定義されています。したがって、前の例(カットを含む)は、より簡単に次のように記述できます。

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

または、キーにベアワードを使用することでさらに短く記述できます。

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

3.6. タグ (Tags)

型は、表現型表記法を使用してCBORタグ(メジャータイプ6)を利用できます。#6.nnn(type) と記述します。ここで、nnnはタグ番号を与える符号なし整数であり、"type" はタグ付けされるデータ項目の型です。

例えば、CDDLプレリュード(付録D)の次の行は、"biguint" を符号なしbignum Nの型名として定義しています。

biguint = #6.2(bstr)

[RFC7049] で定義されたタグはプレリュードに含まれています。[RFC7049] が書かれて以降に登録された追加のタグは、必要に応じてCDDL仕様に追加する必要があります。例えば、仕様内でバイナリUniversally Unique Identifier (UUID) タグを "buuid" として参照するには、次のように定義します。

buuid = #6.37(bstr)

以下の例では、URIに対するタグ32の使用はオプションです。

my_uri = #6.32(tstr) / tstr

3.7. アンラップ (Unwrapping)

マップまたは配列を定義するために使用されるグループは、多くの場合、別のマップまたは配列の定義で再利用できます。同様に、タグとして定義された型は、参照したい内部データ項目を運びます。これらの場合、マップ、配列、またはタグタイプの名前を、その内部で定義されたグループまたは型のハンドルとして単に使用するのが便利です。

「アンラップ」演算子(名前の前にチルダ文字 "~" を付けて記述)を使用すると、名前に定義された型を1層取り除き、基礎となるグループ(マップおよび配列の場合)または型(タグの場合)を露出させることができます。

例えば、アプリケーションが基本ヘッダーと高度なヘッダーを定義したいとします。アンラップを使用しない場合、次のようになります。

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

basic-header = [ basic-header-group ]

advanced-header = [
basic-header-group,
field3: bytes,
field4: number, ; タグ付き型 "time" と同様
]

アンラップを使用すると、これが次のように簡素化されます。

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

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

(後者の例で最初のアンラップ演算子を省略すると、advanced-header内に独自の配列としてbasic-headerがネストされることになりますが、アンラップされたbasic-headerでは、basic-header内のグループの定義が本質的にadvanced-header内で繰り返され、単一の配列になります。これは、プログラミング言語の継承によって解決されることが多いさまざまなアプリケーションに使用できます。アンラップの効果は、参照される型の中にグループまたは型を「スレッドイン」(threading in) することとしても説明でき、これがスレッドのような "~" 文字を示唆しました。)

3.8. 制御 (Controls)

制御 を使用すると、制御演算子 を介して ターゲット 型を コントローラー 型に関連付けることができます。

制御型の構文は "target .control-operator controller" であり、制御演算子はドットで始まる特別な識別子です。(ターゲット または コントローラー は括弧で囲む必要がある場合があることに注意してください。)

この時点でいくつかの制御演算子が定義されています。さらなる制御演算子は、この仕様の新しいバージョンによって定義されるか、または6.1項の手順に従って登録される可能性があります。

3.8.1. 制御演算子 .size

".size" 制御は、制御型によってターゲットのサイズをバイト単位で制御します。この制御はテキスト文字列とバイト文字列に対して定義されており、文字列内のバイト数を直接制御します。また、符号なし整数に対しても定義されています(以下を参照)。図8は、バイト文字列の使用例を示しています。

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

図 8: バイト単位のサイズ制御

符号なし整数に適用される場合、".size" 制御は、その符号なし整数のコンピュータ表現に必要な最大バイト数を与えることによって、その整数の範囲を制限します。つまり、"uint .size N" は "0...BYTES_N" と同等です(ここで BYTES_N == 256**N)。

audio_sample = uint .size 3 ; 24ビット、0...16777216と同等

図 9: 整数のバイトサイズ制御

CDDLの値制限と同様に、この制御は表現の制約ではないことに注意してください。より少ないバイト数に収まる数値でもその形式で表現できますし、効率の悪い実装では、CDDL外の何らかの形式制約([RFC7049]の3.9項のルールなど)によって制限されていない限り、より長い形式を使用する可能性があります。

3.8.2. 制御演算子 .bits

バイト文字列に対する ".bits" 制御は、ターゲット内で、制御型の数値で番号付けされたビットのみがセットされることを許可されていることを示します。(ビットは通常の方法で数えられ、"str" 内でセットされているビット番号 "n" は "(str[n >> 3] & (1 << (n & 7))) != 0" を意味します。)同様に、符号なし整数 "i" に対する ".bits" 制御は、"(i & (1 << n)) != 0" となるすべての符号なし整数 "n" について、"n" が制御型に含まれていなければならないことを示します。

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) ; データオフセットビット

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

図 10: セット可能なビットの制御

付録Fで説明されているCDDLツールは、"tcpflagbytes" に対して次の10個の例インスタンスを生成します。

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

これらの例は、上記のCDDL仕様が2バイトのサイズを明示的に指定していないことを示していません。フラグバイトの有効なオールクリアインスタンスは、"h''" や "h'00'"、さらには "h'000000'" でも構いません。

3.8.3. 制御演算子 .regexp

".regexp" 制御は、ターゲットとして指定されたテキスト文字列が、制御型の値として指定されたXML Schema Definition (XSD) 正規表現と一致する必要があることを示します。XSD正規表現は [W3C.REC-xmlschema-2-20041028] の付録Fで定義されています。

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

図 11: XSD正規表現による制御

この正規表現に一致する例:

3.8.3.1. 使用上の考慮事項

XSD正規表現は、バイトまたはUnicodeコードポイントの16進数表現のための通常の \x または \u エスケープをサポートしていないことに注意してください。ただし、CDDLではXSD正規表現はテキスト文字列に含まれており、そのリテラル表記は \u エスケープを提供します。これは、テキスト文字列に正規表現を使用するほとんどのアプリケーションにとって十分なはずです。(これはまた、XSDエスケープルールが適用される前に、文字列エスケープのレベルが1つあることも意味します。)

XSD正規表現は文字クラスの減算をサポートしています。これは正規表現ライブラリにはないことが多い機能です。仕様作成者はこの機能を慎重に使用したいと思うかもしれません。同様の考慮事項はUnicode文字クラスにも適用されます。これらが使用される場合、CDDLを採用する仕様は、どのUnicodeバージョンが対象であるかを識別すべきです(SHOULD)。

XSD正規表現の頻繁なユーザーではない人にとって、他の驚きには以下のようなものがあるかもしれません。

  • 大文字と小文字を区別しないことの直接的なサポートがない。大文字と小文字を区別しないことはプロトコル設計ではほとんど流行遅れになっていますが、必要な場合もあり、その場合は "[Cc][Aa][Ss][Ee]" のように手動で表現する必要があります。

  • \w や \d などの一般的な文字クラスのサポートはUnicode文字プロパティに基づいています。これはASCIIベースのプロトコルで望まれるものとは異なる場合が多く、驚きにつながる可能性があります。(\s と \S はより従来の慣習的な意味を持っており、"." は行末文字 \r または \n 以外の任意の文字に一致します。)

3.8.3.2. 議論

プログラミングコミュニティでは多くの種類の正規表現が使用されています。例えば、Perl互換正規表現 (PCRE) は広く使用されており、おそらくXSD正規表現よりも有用です。しかし、本文書で使用できるPCREの規範的なリファレンスはありません。代わりに、現時点ではXSD正規表現を選択しています。IETFには、YANG [RFC7950] など、その選択の前例があります。

CDDLはその主な拡張ポイントとして制御を使用していることに注意してください。これにより、必要に応じてここで参照されているものに加えて、さらなる正規表現形式を追加する機会が生まれます。例として、".pcre" 制御の提案が [CDDL-Freezer] で定義されています。

3.8.4. 制御演算子 .cbor と .cborseq

バイト文字列に対する ".cbor" 制御は、バイト文字列がCBORエンコードされたデータ項目を運ぶことを示します。デコードされると、データ項目は右側の引数として指定された型(次の例のtype1)と一致します。

bytes .cbor type1

同様に、バイト文字列に対する ".cborseq" 制御は、バイト文字列がCBORエンコードされたデータ項目のシーケンスを運ぶことを示します。データ項目を配列として取得すると、配列は右側の引数として指定された型(次の例のtype2)と一致します。

bytes .cborseq type2

(エンコードされたシーケンスの配列への変換は、例えば、バイト文字列を2つのバイト0x9fと0xffの間にラップし、ラップされたバイト文字列をCBORエンコードされたデータ項目としてデコードすることによって実行できます。)

3.8.5. 制御演算子 .within と .and

型に対する ".and" 制御は、データ項目が左側の型と右側として指定された型の両方に一致することを示します。(形式的には、結果の型は指定された2つの型の共通部分です。)

type1 .and type2

".and" 制御のバリエーションは ".within" 制御であり、これは追加の意図を表現します。左側の型は右側の型のサブセットであることを意図しています。

type1 .within type2

両方の形式は同一の形式的セマンティクス(共通部分)を持っていますが、".within" 形式の意図は、右側が左側で許可される型へのガイダンスを与えることであり、これは通常ソケット(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]

".within" の場合、type1がtype2で許可されていないデータ項目を許可していると、ツールはエラーのフラグを立てる可能性があります。対照的に、".and" の場合、type1がすでにtype2のサブセットであるという期待はありません。

3.8.6. 制御演算子 .lt, .le, .gt, .ge, .eq, .ne, および .default

制御 .lt, .le, .gt, .ge, .eq, および .ne は、左側の型に対する制約を指定し、右側の型(その単一の値のみを含む)として指定された値より小さい、以下、より大きい、以上、等しい、または等しくない値である必要があることを指定します。現在の仕様では、最初の4つの制御(.lt, .le, .gt, および .ge)は数値型に対してのみ定義されています。これらには自然な順序関係があるためです。

speed = number .ge 0  ; 単位: m/s

.ne と .eq は、数値とその他の型の値の両方に対して定義されています。値の1つが数値型でない場合、等価性は次のように決定されます。テキスト文字列は、バイト単位で同一である場合に等しい(.eqを満たす/.neを満たさない)。バイト文字列についても同様です。配列は、同じ数の要素を持ち、すべての要素が配列間でペアごとに順序どおりに等しい場合に等しくなります。マップは、同じ数のキー/値ペアを持ち、2つのマップ間でキー/値ペア間にペアごとの等価性がある場合に等しくなります。タグ付きの値は、同じタグを持ち、値が等しい場合に等しくなります。単純型の値は、同じ値である場合に一致します。配列、マップ、またはタグ付きの値の中に現れる数値型は、数値が等しく、両方が整数であるか両方が浮動小数点値である場合に等しくなります。その他のすべてのケースは等しくありません(例:テキスト文字列とバイト文字列の比較)。

".ne" 制御のバリエーションは ".default" 制御であり、これは追加の意図を表現します。右側の型で指定された値は、指定された左側の型のデフォルト値として意図されており、暗黙の .ne 制御はこの値がワイヤー上で送信されるのを防ぐためにあります。この制御は、制御型がオプションのコンテキストで使用される場合にのみ意味があります。そうでない場合、デフォルト値を利用する方法がありません。

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

3.9. ソケット/プラグ (Socket/Plug)

型選択とグループ選択の両方について、最初は空の選択から始めて後でそれらを組み立てることを容易にするメカニズムが定義されています。場合によっては、完全な仕様を構築するために連結される別々のファイルで行われます。

慣習により、CDDL拡張ポイントは、先頭のドル記号(型)または2つの先頭のドル記号(グループ)でマークされます。ツールはその慣習を尊重し、そのような型またはグループがまったく定義されていない場合でもエラーを発生させません。その場合、シンボルは空の型選択(グループ選択)とみなされます。つまり、選択肢はありません。

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

; 後で、別のファイルで

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

; そして、おそらく別のファイルで

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

単一の "$" で始まる名前は「型ソケット」(type sockets) であり、空の型として始まり、"/=" を介して拡張されることを意図しています。二重の "$$" で始まる名前は「グループソケット」(group sockets) であり、空のグループ選択として始まり、"//=" を介して拡張されることを意図しています。いずれの場合も、ソケットの定義がまったくないことはエラーではありません。これは、ルールを満たす方法がない(つまり、選択が空である)ことを意味します。

慣習として、ソケット名のすべての定義(プラグ)は拡張である必要があります。つまり、それぞれ "/=" と "//=" を使用する必要があります。

図7で説明した例を取り上げると、ソケット/プラグメカニズムは図12のように使用できます。

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

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

; 上記はすでにそのまま機能します。
; しかし、後で追加できます:

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

; そして再び、他の場所で:

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

図 12: 個人データ例:ソケット/プラグ拡張性の使用

3.10. ジェネリクス (Generics)

山括弧を使用して、ルールの左側は、定義される名前の後に仮引数を追加できます。

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

ジェネリクスルールを使用する場合、仮引数はジェネリクスルールのスコープ内で、提供された実引数にバインドされます(山括弧も使用)(parameter = argument の形式のルールがあるかのように)。

ジェネリクスルールは、型とグループの両方の名前を確立するために使用できます。

(現時点では、付録Fで説明されているCDDLツールのジェネリクスのネストにはいくつかの制限があります。)

3.11. 演算子の優先順位

前置演算子や中置演算子などの複数の構文機能を持つ他の言語と同様に、CDDLには他の演算子よりも強く結合する演算子があります。CDDLには型とグループの両方があり、これらの概念に固有の演算子があるため、これはABNFなどよりも複雑になっています。型演算子(型選択のための "/" など)は型に作用し、グループ演算子(グループ選択のための "//" など)はグループに作用します。型はグループ内で単純に使用できますが、グループを型にするにはブラケット(配列またはマップとして)で囲む必要があります。したがって、型演算子は自然にグループ演算子よりも強く結合します。

例えば、

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

において、group1 は、aとbの型選択と、cとdの型選択の間のグループ選択です。メンバーキーや出現回数が追加されると、これはより関連性が高くなります。

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

は、型aまたはbのオプションのメンバー "ab" と、型cまたはdのメンバー "cd" の間のグループ選択です。オプション性は最初の選択("ab")に付随しており、2番目の選択には付随していないことに注意してください。

同様に、

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

において、group3 はa、b、cの間の型選択の繰り返しです。aだけを繰り返し可能にする場合、出現を集中させるためにグループ選択が必要です。

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

group4 は、繰り返し可能な a と、単一の b または c の間のグループ選択です。

group3 のセマンティクスは直感的ではない可能性があるというコメントがありました。一般に、演算子優先順位ルールを持つ他の多くの言語と同様に、仕様作成者はそれらに依存せず、CDDL優先順位ルールに詳しくない読者を導くために括弧を自由に使用することが推奨されます。

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

演算子の優先順位は、結合の緩い順に付録Bで定義され、表1に要約されています。(指定されたアリティは、単項前置演算子の場合は1、二項中置演算子の場合は2です。)

演算子アリティ操作対象優先順位
=2name = type, name = group1
/=2name /= type1
//=2name //= group1
//2group // group2
,2group, group3
*1* group4
n*m1n*m group4
+1+ group4
?1? group4
=>2type => type5
:2name: type5
/2type / type6
..2type..type7
...2type...type7
.ctrl2type .ctrl type7
&1&group8
~1~type8

表 1: 演算子の優先順位の概要