CATS DSL⚓︎
- CATS
- CATS DSL(CATS は Compact Affinitized Transfer Schema というユーモラスな逆頭字語で、DSL は Domain-Specific Language の略です)は、構造化データのバイナリレイアウトを定義するためのコンパクトで記述的な言語です。
もともと Symbol と NEM のために開発され、両プロトコルのすべてのブロックとトランザクションの仕様に使われていますが、設計は十分に汎用的で、任意のバイナリ形式を記述できます。
CATS はサイズ効率、性能、厳密な型付けを優先し、可能な場合はゼロコピーのデシリアライズを目指します。 固定サイズバッファー、厳密な型エイリアス、インライン構造、条件付きフィールドなどの機能があります。
CATS 定義は ジェネレーター で処理されます。ジェネレーターは、CATS で定義したバイナリ構造をネイティブ言語の構造へシリアライズ(書き込み)またはデシリアライズ(読み取り)できるように、特定のプログラミング言語のコードを生成するツールです。
現在は Python と JavaScript/TypeScript 用のジェネレーターがあり、Java 用は開発中です(2025年6月時点)。 これらは NEM SDK で使われ、プラットフォーム間で一貫した効率的なバイナリエンコードを保証します。
このページでは CATS DSL の構文と機能を説明します。 完全な精度が必要な場合は、Symbol のソースリポジトリに Lark 構文解析言語 で記述された 正確な文法 があります。
空白
すべての CATS 文は改行で終わります(セミコロンは使いません)が、それ以外では空白は意味を持ちません。
構文解析器にインデントは必要ありませんが、通常は明確さを加えるために使用します。
CATS ファイルは、トップレベルの4つのキーワード import、using、enum、struct で構成されます。
それぞれについて、以下のセクションで説明します。
import⚓︎
import 文を使うと、CATS ファイルに他の CATS ファイルを含められます。
これにより、スキーマ定義をモジュール化して再利用できます。
別の CATS ファイルをインポートするには、ファイル名を引用符で指定します。
インポートしたファイル名は、構文解析器に渡されたインクルードパスを基準に解決されます。
using⚓︎
using 文は、組み込みプリミティブ型の 型エイリアス を定義します。
これらのエイリアスは構文解析器とジェネレーターでは別の型として扱われるため、2つの型が同じ基礎表現を共有していても厳密な型付けが可能です。
CATS は組み込み型を次の2カテゴリでエイリアス化できます。
- 整数型:
- 符号なし:
uint8、uint16、uint32、uint64 - 符号付き:
int8、int16、int32、int64
- 符号なし:
- 固定サイズバイナリバッファー:
binary_fixed(N)は N バイト長のバッファーを定義します。
たとえば、8バイトの符号なし整数として Height 型を定義します。
32バイトのバイナリバッファーとして PublicKey 型を定義します。
次の例では Height と Weight はどちらも uint64 に基づきますが、別の型 として扱われ、相互に入れ替えて使用できません。
enum⚓︎
enum 文は、整数型を基礎とする名前付き定数で構成される型、つまり 列挙型 を定義します。
各列挙型では基礎型を明示する必要があり、組み込み整数型のいずれかを使用できます。
列挙型のメンバーは enum 宣言の下の行に定義します。
各メンバーには定数の整数値を割り当てる必要があります。
たとえば、32ビット符号なし整数を基礎型とする TransportMode 列挙型を定義します。
列挙型属性⚓︎
列挙型は動作を変更する属性をサポートします。
各属性は @ で始まり、列挙型宣言の上の行に記述する必要があります。
現在サポートされている属性は次の1つだけです。
-
@is_bitwise:列挙型がビットフィールド(フラグの集合)を表し、生成コードでビット演算をサポートすることを示します。例:
これは、ジェネレーターに列挙値をビット単位の OR で結合でき、個々のフラグをビット単位の AND で確認できることを伝えます。
struct⚓︎
struct 文は、名前付きフィールドで構成される 構造化バイナリレイアウト を定義します。
構造体は CATS の最も重要な構成要素です。トランザクション、ブロック、その他すべての複合オブジェクトを記述するために使われます。
各構造体宣言は、任意で 修飾子 が前に付く struct キーワードで始まります。
宣言の後の行で、フィールド名と型を指定してフィールドを定義します。
例:
修飾子⚓︎
CATS は次の修飾子をサポートします。
-
abstract:継承用の基底構造体を定義します。 ジェネレーターは、適切な派生型をインスタンス化するファクトリーを生成します。 -
inline:構造体が合成にだけ使われ、独立した型として出力されないことを示します。
修飾子を指定しなければ、構造体はそのまま生成出力に含まれます。
特別なフィールドコンストラクター⚓︎
型の代わりに、特別なコンストラクターを使ってフィールドを宣言することもできます。
-
make_const(type, value):定数を定義します。 このフィールドはレイアウトに現れません。代わりに、生成コードで<StructName>.<FieldName>としてアクセスできる定数になります。次の例では
TRANSPORT_MODEはシリアライズされませんが、ROAD値を持つTransportMode型のCar.TRANSPORT_MODE定数になります。 -
make_reserved(type, value):固定値を持つ予約フィールドを定義します。 このフィールドはレイアウトに保存され、常に指定された値になります。次の例では、フィールド
wheel_countが固定値4のuint8として保存されます。 -
sizeof(type, reference):別のフィールドのサイズ(バイト)で自動的に埋められるフィールドを定義します。 参照する型を変更してもサイズフィールドを手動で更新する必要がないため、構造体の保守が簡単になります。ここで
car_sizeは、Car型のフィールドcarのサイズ(バイト)を常に保持するuint16です。
条件付きフィールド⚓︎
別のフィールドの値に基づいて、条件付きで存在するフィールドを作成できます。 他の言語の共用体に似た、相互排他的なレイアウトを表せます。
条件付きフィールドの構文は次のとおりです。
CATS は次の条件演算子をサポートします。
equals:セレクターフィールドが定数値と完全に一致する場合にフィールドを含めます。not equals:セレクターフィールドが定数値と一致しない場合にフィールドを含めます。in:セレクターフィールドに定数が含まれる場合にフィールドを含めます(ビットフラグ用)。not in:セレクターフィールドに定数が含まれない場合にフィールドを含めます。
たとえば、transport_mode が SEA と等しい場合だけ buoyancy フィールドが含まれます。
配列フィールド⚓︎
CATS は、すべての要素が同じ型を持つ、静的サイズと動的サイズの両方の配列をサポートします。
構文は次のとおりです。
<NumberOfElements> には次を指定できます。
-
要素数を固定する定数。
-
別のフィールドへの参照。動的サイズの配列になります。
たとえば次の構造体は、
vehicles_count個のVehicle型要素を含むvehiclesフィールドを定義します。 -
特別なキーワード
__FILL__。構造体の末尾まで配列を拡張することを示します。この場合、構造体に 下記 の
@size属性を付け、合計サイズ(バイト)を保持するフィールドを参照する必要があります。
Note
<ElementType> には次のいずれかを指定する必要があります。
- 固定サイズ構造体。
- 独自の
@size属性が付いた可変サイズ構造体。
それ以外の場合、構文解析器はバイトストリームから読み取る要素数を判断できません。
配列フィールド属性⚓︎
配列フィールドには、サイズ、アラインメント、ソート方法を制御する属性を付けられます。
サポートされる属性には次があります。
@is_byte_constrained:配列サイズを要素数ではなくバイト数として解釈します。-
@alignment(x [, [not] pad_last]):要素を x バイト境界に揃え、任意で最後の要素にパディングを付けます。デフォルトでは、アラインメントを使うと最後の要素にパディングが付きます。
not pad_last修飾子で無効にできます。 -
@sort_key(x):指定したプロパティで配列がソートされるようにします。たとえば、次の
Vehicle構造体の配列は weight でソートされます。
インライン⚓︎
inline 修飾子を使うと、ある構造体を別の構造体の中に インライン化 できます。
これにより、入れ子にせずに1つの構造体のフィールドを別の構造体へ直接挿入できます。
たとえば、次の定義は Vehicle の内容を Car にインライン化します。
struct Vehicle
weight = uint32
struct Car
inline Vehicle
max_clearance = Height
has_left_steering_wheel = uint8
インライン化されたフィールドはその場所で展開されるため、Car の最終レイアウトは次と同じです。
名前付きインライン
構造体は 名前 を付けてインライン化することもでき、その接頭辞でフィールド名が変更されます。
次の例では SizePrefixedString を friendly_name として Vehicle にインライン化します。
struct SizePrefixedString
size = uint32
__value__ = array(int8, size)
struct Vehicle
weight = uint32
friendly_name = inline SizePrefixedString
year = uint16
次のように展開されます。
struct Vehicle
weight = uint32
friendly_name_size = uint32
friendly_name = array(int8, friendly_name_size)
year = uint16
特別なフィールド __value__ は、インラインに指定された名前(friendly_name)に変更されます。
それ以外のフィールドは接頭辞とアンダースコアで変更されます。たとえば size は friendly_name_size になります。
構造体属性⚓︎
構造体には、コードジェネレーターへのヒントやレイアウト動作への影響を与える属性を含められます。
属性は @ で始まり、struct 宣言の上に記述します。
CATS は次の構造体レベル属性をサポートします。
@is_aligned:すべてのフィールドを自然な境界に揃えます。@is_size_implicit:構造体をsizeof(type, field)式で参照できるようにします。@size(x):フィールドxが構造体全体のサイズ(バイト)を保持することを宣言します。@initializes(x, Y):別の場所で定義された定数Yでフィールドxを初期化します。@discriminator(x [, y...]):abstract構造体で使い、指定したプロパティに基づいてデコード時に適切な派生型を選択します。@comparer(x [!transform] [, y...]):インスタンスのソートまたは比較に使うプロパティを定義します。 任意の変換はプロパティ比較の前に適用されます。 現在サポートされている変換は、NEM との後方互換性のためのripemd_keccak_256だけです。
たとえば、次は Vehicle のフィールド transport_mode を派生構造体に定義された定数へリンクします。
@initializes(transport_mode, TRANSPORT_MODE)
abstract struct Vehicle
transport_mode = TransportMode
struct Car
TRANSPORT_MODE = make_const(TransportMode, ROAD)
inline Vehicle
定数 TRANSPORT_MODE は Vehicle を拡張する任意の構造体で定義できます。
整数フィールド属性⚓︎
整数フィールドは1つの属性をサポートします。
-
@sizeref(x [, y]):フィールドの値をxのサイズに設定し、任意でオフセットyを加えます。たとえば、
vehicle_sizeとvehicleの合計サイズを保存します。
コメント⚓︎
# で始まる行はコメントとして扱われます。
宣言の直上にないコメントは構文解析器に無視されます。 ただし、宣言またはフィールドの直前にコメントを置くと ドキュメント として扱われ、生成出力に保持されることがあります。
例:
# This comment is ignored
# This comment is included as documentation
# and will be associated with the `#!cats Height` alias.
using Height = uint64
この規約により、バイナリレイアウトに影響を与えずにスキーマへインラインドキュメントを追加できます。