モジュールとインポート
wirespec はモジュールシステムで複数ファイル構成をサポートします。各 .wspecファイルは名前付きモジュールに属し、型をモジュール間でインポートできます。
モジュール宣言
.wspec ファイルの先頭で module を宣言します:
module quic.varint
@strict
type VarInt = {
prefix: bits[2],
value: match prefix {
0b00 => bits[6],
0b01 => bits[14],
0b10 => bits[30],
0b11 => bits[62],
},
}ドット区切りのモジュール名はファイルパスに直接対応します。quic.varint → quic/varint.wspec(または quic/varint.wspec)。ドットがディレクトリセパレータです。
| モジュール名 | ファイルパス |
|---|---|
quic.varint | quic/varint.wspec |
quic.frames | quic/frames.wspec |
mpquic.path | mpquic/path.wspec |
net.udp | net/udp.wspec |
インポート
import で別モジュールの型をスコープに取り込みます:
module quic.frames
@endian big
import quic.varint.VarInt
const MAX_CID_LENGTH: u8 = 20
packet AckRange {
gap: VarInt,
ack_range: VarInt,
}
frame QuicFrame = match frame_type: VarInt {
0x00 => Padding {},
0x01 => Ping {},
0x06 => Crypto {
offset: VarInt,
length: VarInt,
data: bytes[length],
},
# ...
_ => Unknown { data: bytes[remaining] },
}インポートパス quic.varint.VarInt の形式は module.name.TypeName。最後がインポートする型名、それ以前がモジュール名です。
複数のインポートは 1 行ずつ:
module myapp.protocol
import quic.varint.VarInt
import ble.att.AttHandle
import net.udp.UdpDatagramファイルパスの解決
import quic.varint.VarInt を見つけると、コンパイラはモジュール名 quic.varint を以下の手順でファイルに解決します:
- ドットをディレクトリセパレータに変換:
quic.varint→quic/varint .wspecを付加:quic/varint.wspec- インクルードパスを順に検索
デフォルトのインクルードパスはカレントディレクトリです。-I path/ で検索ルートを追加:
# Resolves quic/varint.wspec relative to examples/
wirespec compile examples/quic/frames.wspec -I examples/ -o build/どのインクルードパスにもファイルが見つからなければエラーになります:
error: module 'quic.varint' not found
searched: ./, examples/複数モジュールプロジェクトのコンパイル
単一モジュール(インポートなし)
wirespec compile examples/net/udp.wspec -o build/インポートありのモジュール
ルートモジュールを指定し、依存モジュールを含むインクルードパスを追加します:
wirespec compile examples/quic/frames.wspec -I examples/ -o build/コンパイラが全インポートを解決し、依存モジュールをコンパイルして出力を生成します。
再帰モード
--recursive で指定ファイルと全推移的依存をまとめてコンパイルします:
wirespec compile examples/quic/frames.wspec -I examples/ --recursive -o build/依存が深い大規模プロジェクトで便利です。
コード生成なしのチェック
wirespec check examples/quic/frames.wspec出力ファイルを書かずにファイルをパース・型チェックします。CI で .wspec ファイルだけ検証したいときに便利です。check コマンドは入力ファイルのみを受け取り、-I フラグには対応していません。
生成される出力
各モジュールから .h / .c のペアが生成されます。モジュール名がファイルプレフィックスとなり、ドットはアンダースコアに置換:
build/
quic_varint.h # quic.varint → quic_varint prefix
quic_varint.c
quic_frames.h # quic.frames → quic_frames prefix
quic_frames.cquic.frames が quic.varint をインポートしている場合、生成される quic_frames.h は依存を自動インクルードします:
/* Auto-generated by wirespec compiler -- DO NOT EDIT */
#ifndef WIRESPEC_QUIC_FRAMES_H
#define WIRESPEC_QUIC_FRAMES_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include "wirespec_runtime.h"
#include "quic_varint.h" /* imported: quic.varint.VarInt */
/* ... struct and function declarations ... */
#endif /* WIRESPEC_QUIC_FRAMES_H */#include の手動管理は不要です。コンパイラが依存を追跡して正しいインクルードを生成します。
エクスポートと可視性
デフォルトではモジュール内の全型がインポート可能です。内部型を隠したい場合は、公開する定義に export を付けます:
module mylib.codec
# Only PublicHeader is importable from outside this module
export packet PublicHeader {
version: u8,
length: u16,
}
# InternalHelper is not visible to importers
packet InternalHelper {
checksum: u32,
}規則:
- モジュール内のいずれかのアイテムに
exportがある → エクスポートされたものだけが外部から見える - どのアイテムにも
exportがない → 全て公開(後方互換)
既存モジュールに段階的に可視性を導入できます。公開したいアイテムに export を付ければ、残りは自動的に非公開になります。
循環参照の検出
循環インポートはコンパイル時に検出されエラーになります:
error: circular import detected: a → b → a循環依存はサポートされません。共有型を共通の基底モジュールに切り出してください:
# shared/types.wspec
module shared.types
export type Handle = u16le
# module_a.wspec
module module_a
import shared.types.Handle
# module_b.wspec
module module_b
import shared.types.Handle例: QUIC VarInt + フレーム
QUIC の例は 2 モジュール構成です。quic/varint.wspec で可変長整数を定義し、quic/frames.wspec がインポートします:
# Compile frames.wspec; the compiler finds varint.wspec via -I examples/
wirespec compile examples/quic/frames.wspec -I examples/ -o build/
# Build the generated C alongside your tests
cd build
gcc -Wall -Wextra -Werror -O2 -std=c11 -I../runtime \
-o test_frames quic_varint.c quic_frames.c test_quic_frames.c
./test_frames依存順序は自動的に処理されます。quic_frames.c が quic_varint.c に依存するので、正しい順序で出力されます。