コンテンツにスキップ

5. 構文

1 行が 1 コマンドで、# は行コメントを開始します。行はコマンドキーワードで始まり、 残りの引数はすべて key=value でなければなりません。

window side=front mat_slot=glass offset=2 y=2 size=2x2 sym=true # OK
window front G 2 2 2x2 # 禁止 (位置引数)

位置引数は引数順を覚える必要があり、LLM は順序を幻覚し、引数を落とします。mat= や side= の ようなキーは attention のアンカーとして働いて生成を安定させます。消費するトークン以上の価値が あります。

位置値を読まない行に裸の値があると E_UNEXPECTED_POSITIONAL です。位置値の読み手を持つ形式は connect FROM.PORT to TO.PORT だけで、その形状は E_CONNECT_ARITY が検査します。

パーサは key= / -> binding / [selector] のいずれでもないトークンをすべて位置値のリストに 入れるので、= の打ち漏らしもここに落ちます。walls mat_slot=wall height 3 は高さの縮んだ walls ではなく、まったく建ちません。

サイズリテラルはちょうど 2 つの寸法 WxH です。2 つ目より先に続く並び (2x2x9 や 2x2y) は、 サイズとその後ろの何かとして読まず、リテラルの位置で拒否します。

カンマは 任意の区切り であって構造ではありません。mat=[a, b] と mat=[a b] は同じ 2 要素を 指し、[side=front, y=2] と [side=front y=2] も同様です。ただし許容する約物の量は 2 つのリストで 違います。

  • [selector] の属性リストはカンマを見つけた場所で読み飛ばすので、[side=front, , y=2] は通ります。
  • 値リストは要素間に高々 1 個しか読まず、[a, , b] を拒否します。

正準トークンには ブロックステートリテラル を付けられます (@oak_log[axis=x]、 @oak_stairs[half=top, facing=north])。[ はトークンに接していなければなりません。空白の後の [ は次に来る別のものなので、値リストでは [@a [b]] は今まで通りトークンと入れ子リストですが、 [@a[b]] は b がプロパティではないため拒否されます。中の各組は property=value で、値は単語、 数字の並び、true / false のいずれかです。これは Cairn のリストではなく Minecraft 自身の ブロックステート構文なので、2 つの組の間にはちょうど 1 つのカンマを書きます。空のリテラル、末尾や 重複したカンマ、同じプロパティを 2 回書くことは拒否されます。@floor.wood のようなドット付きの トークンは抽象トークンで、ブロックを選ぶのはそれを束縛するテーマなので、リテラルを取りません。 そのため、それに接する [ は、空白の後の [ と同じく次に来る別のものです。

リテラルが入る前は、ドットのないトークンに接する [ も次に来る別のものでした。そのため mat=@a[1] と mat=[@a[b]] は以前はパースでき、今は拒否されます。cairn check を通ったソースに どちらの形もありません。裸の値を読まない行の裸の値は E_UNEXPECTED_POSITIONAL、ラベルを書く場所の リストは E_TYPE_MISMATCH_LABEL になるからです。変わるのは、ビルドできなかったソースのパース木 だけです。

リテラルのプロパティと値はまだターゲットに照らして検査されません。E_STATE_DOMAIN (バージョンとエディション) は未実装なので、Java のビルドは @oak_log[axis=q] をそのまま書き出します。ビルドが読むリテラルにはそれぞれトークンの位置に W_STATE_LITERAL_UNCHECKED (Lint) が付くので、黙って書き出されはしません。

このリテラルを除けば、カンマが意味を持つのは assert truth(...) の入力リストだけで、そこでは 行の幅と照合される信号の個数を区切ります。行は入力信号 1 つにつき 1 文字 — 0、1、- — を 書くので、truth(a, b -> out) の行は 2 文字幅であり、{ 2->0 } や { 0->0 } は拒否されます。

- は don’t-care です。その入力のどの値でもよいという意味なので、0- -> 1 は 00->1 と 01->1 を合わせたものを言います。それ自体が新しい構文ではなく、行の省略形です。だからこそ 2 つの行が同じ組を担当することはできません (下の表)。

- と -> は 1 文字を共有しており、字句解析は可能なかぎり矢印を取ります。最後の入力が don’t-care である行は 11--> 0 または 11- -> 0 と書き、どちらも同じ 3 文字幅の行です。 空白はパターンを終わらせるので、0- 1 -> 1 は 3 文字幅の行ではなく、2 文字幅のパターンと 余った 1 です。

行の出力も 0、1、- の 3 つで、こちらの - は別の意味を持ちます。その行が担当する組を 意図的に制約しない、という意味です。--0 -> - は、3 本目の入力が低いどの組についても表は何も 言わない、と述べます。これが W_TRUTH_TABLE_PARTIAL への答えになり、author が意図していない 4 つの出力を主張せずに済みます。ここでは矢印を既に読み終えているので、-> - と ->- は同じ行 です。すべての行の出力が - である表は何も制約しないので、行が 1 つも無い表と同じく E_TRUTH_TABLE_EMPTY です。

行を囲む表も同じ読み方をします。

状況 コード
行が 1 つも無い、または出力が 0 か 1 の行が 1 つも無い E_TRUTH_TABLE_EMPTY
2 つの行が同じ入力の組に違う出力を割り当てている E_TRUTH_TABLE_CONFLICT (後ろの行で報告)
2 つの行が同じ入力の組を担当し、かつ矛盾していない W_TRUTH_TABLE_DUPLICATE_ROW (後ろの行で報告)
割り当てられていない入力の組がある W_TRUTH_TABLE_PARTIAL

後ろの 2 つが警告なのは、書かれている行はどれも本物の主張だからです。入力 4 本の表は 16 行あり、 途中まで書いた作者を止める理由はありません。

インデントは 1 レベル 2 スペースで、1 度に 1 レベルだけ開きます。2 の倍数でない幅と、2 レベル以上の 飛び越しは別の誤りなので、別々に報告します。

改行の前のスペースは行の一部ではないので、行はスペースで終わってもよく、スペースだけの行は空行です。 空行の行頭スペースも他の行と同じように数えられますが、行ごと捨てられるため、その幅はインデントでも 誤りでもありません。

ファイル先頭の UTF-8 BOM は無視します。それ以外の位置にあるものは、ただの迷子の文字です。

行の終わりは \n、\r\n、単独の \r のいずれでもよく、3 つとも同じ改行として扱います。VS Code や Monaco と同じ規則なので、診断の行番号とカーソル位置の行番号は一致します。位置は常に誤っているテキス トそのものを指すので、行末のエラーはその行の行末で報告され、次の行の第 1 桁になることはありません (tree-sitter 文法は既知の例外です。ランタイムが \n でしか行を進めないため、単独の \r だけで終端 されたファイルは、正しくパースされていても 1 本の長い行としてハイライトされます)。

ネストは浅く保ちます (struct / def / level / theme / site)。深いネストは LLM の生成誤りを 増やします (room はこのリストにありません。まだ未決なので、今日書けば E_UNKNOWN_KEYWORD です。 未決事項 を参照)。

ボディの中でメンバをグループ化するのは level y=N だけで、それも struct と def の中に限ります。 site のボディは place と connect のフラットなリストで、グループ化構文はありません。それ以外の 場所のインデントされたボディはサイレントに落とすのではなく E_UNSUPPORTED_NESTING です。ブロックも 生まず、配置もされず、walkway も敷きません。

この規則はメンバについてのものなので、メンバを持つボディにしか届きません。theme のボディが持つ のはルールで、素材を束ねるだけで何も開かないため、その下にインデントされた行はネストの診断ではなく 構文エラーです。ディレクティブの下にインデントされた行も同じで、ディレクティブは 1 行であり、その 下の行はどの構文にも属しません。

y=N が各メンバに何を意味するかは コンパイルモデル §4.7 を参照してください。

どのキーワードをボディが受け付けるか も同じ切り分けに従います。struct / def のボディは 1 棟 のジオメトリを記述します (floor / walls / door / window / roof / stair / level / pressure_plate / circuit)。site のボディはレイアウトを記述します (place / connect)。キーワー ド表はグローバルなので、片方をもう片方のボディに書くとパースも分類も通り、そのうえで誰にも届きません。 これが E_MISPLACED_MEMBER で、該当行で 1 回だけ報告され、その下にインデントされたものも一緒に落ち ます。

logic と assert の行はメンバではないので、この規則は届きません。logic 行はどちらのボディからも レッドストーン合成が読み、assert はまだ誰も読みません。

トップレベルの名前は種別ごとにスコープされます。 theme / def / struct / site は 4 つの名 前空間なので、1 つの名前は各種別に 1 回ずつ現れられます。同じ種別で 2 回宣言すると E_DUPLICATE_ITEM です。theme / def / struct では名前が束縛キーなので、最初の宣言が解決し、繰 り返しは何の合図も無く消えることになります。同名の site ブロック 2 つは代わりにマージされ、place は 1 つの site::NAME:: 名前空間を共有します。ただし east_of= はブロックをまたいで届かないので、 マージは半分だけであり、やはりエラーです。

メタデータは意味的な本体ではなくヘッダに置けます。

@cairn 2026.06 # このファイルが書かれた対象の Cairn 言語バージョン
@requires version>=1.20 # Minecraft ターゲットへの能力下限
@intended_targets ["1.20.4","1.21.4"] # ヒントであって検証記録ではない

@cairn は Cairn 言語自体のバージョンで、Minecraft 側の 2 つのヘッダとは別の軸です。任意であり、 将来のコンパイラが正しくパース/警告できるようにするための provenance として存在します。値で分岐する パスはありませんが、値は読まれます。YYYY.M または YYYY.M.PATCH で、年は 4 桁、月は 1 … 12 です。 月の先頭のゼロは受け付けられるので、2026.06 と 2026.6 は同じバージョンです。それ以外は W_INVALID_CAIRN_VERSION、読んでいるコンパイラより新しいバージョンは W_FUTURE_CAIRN_VERSION です。 後のコンパイラに読めない provenance は、このヘッダが存在する理由を果たしていません。

@requires は能力の下限です。式は任意のエディション、主語 version、演算子 >=、バージョン ラベルからなり、間の空白は任意です (version>=1.21 と version >= 1.21 は同じ要求)。演算子が >= だけなのは、最も厳しいものへ畳み込んで合成できる制約が下限だけだからです。それ以外の式は、何も宣言し ない行を黙って残すのではなく E_INVALID_REQUIRES です。消えてしまう下限は、無い下限より悪いのです。 読み手はそれを信じてしまいます。

@requires version>=1.21 # ビルドされるエディションに対する下限
@requires java version>=1.21.4 # Java の採番での下限。Bedrock ビルドでは不活性
@requires bedrock version>=1.21.40

エディションを書けるのは、Java のリリースが 1.20.4 / 1.21 / 1.21.4、Bedrock が 1.21.0 / 1.21.40 / 1.21.60 と進むからです。1.21.4 は Java の最新リリースで、Bedrock のどのリリー スも名指しません。ラベルは、各要素が数字で始まり英数字だけからなるドット区切りの列で、任意で - と 同じ形のプレリリースタグが続きます。1.21.4、1.21.4-rc1、24w14a はいずれもラベルです。そのうち どれを ターゲットのエディション が順序付けられるかは構文の問題ではなく、ここでは答えません。 バージョンとエディション を参照してください。

@intended_targets はどの Minecraft バージョン向けに設計されたかを示します。検証済みだという主 張ではありません。その記録はロックにのみ存在します。

ヒントであっても、隣の下限には照らされます。@intended_targets の名指すバージョンのうちターゲットの エディションがビルドできるものを @requires がすべて拒否するファイルは、誰かがそれに従って動いた瞬間 にコンパイラが拒否する意図を表明しており、それが E_INTENDED_TARGET_CAP です。一部だけが下限を下回るリストは W_INTENDED_TARGET_CAP、ターゲットのエ ディションがそもそもビルドできないバージョンは W_INTENDED_TARGET_UNSUPPORTED になります。 バージョンとエディション §10.4 を参照してください。

@cairn と @intended_targets はモジュールにつき最大 1 回で、繰り返しは E_DUPLICATE_HEADER です。 @requires は例外で、下限は合成されるため、繰り返しは置き換えではなく制約の追加になります。

def と theme は同じ式を本体の行として持てます。綴りは @ を除いた requires です。これはファイ ルではなくその構成要素に対する下限で、それをインスタンス化するすべてのビルドに継承されます。ファイル のディレクティブであることを示すのが接頭辞の @ であり、構成要素の下限はそれではありません。 バージョンとエディション を参照してください。

壁のセレクタは front (+z) / back / left / right です。offset は壁に沿って走り、y は床 (y = 0) から測ります。内側の面は接頭辞を付けます (inside.front)。ブロック、ブロックエンティティ、エンティ ティはすべて同じセレクタ文法を使います。

offset の原点。 offset=0 は、その壁を外から見たときの左端です。front と back の壁は低い x を基準にし、front は +z 視点、back は x 方向に鏡映するので、sym=true の開口は建物のどちら 側から見ても対称に見えます。left と right の壁は低い z を基準に、同じように鏡映します。

sym=true は開口を壁の中点で鏡映します (mirror_offset = wall_length - offset - size_w)。鏡映が元 の矩形と重なる場合は W_DEFERRED_MEMBER で拒否し、元の側だけを塗ります。sym= は裸の true か false を取り、書かれていない窓は鏡映されません。それ以外の値 (sym=yes、sym="true"、 sym=1) は読めない値で、窓は鏡映なしで描かれ、その値は W_IGNORED_ARGUMENT で報告されます (Lint §11.3)。

at= によるドアのアンカー。 ドアの壁ローカル列は 3 つの名前付きアンカーから決まります。

アンカー 列
at=center wall_length / 2 の四捨五入 (0.5 は切り上げ)。奇数長の壁には一意な中心があり、偶数長の壁は中点の右の列を選びます。
at=left 壁ローカル軸の原点 u = 0。
at=right 遠い方の角 u = wall_length - 1。

同じ列が、開口の切り抜きと、このドアに接続される connect の walkway (§9.3.5) の両方を解決します。数値オフセット (at=N) は将来の拡張のために予約されています。

ドアが開く段。 level y=N の下のドアが開くのはその level の基底段の 1 つ上、段 N + 1 です。 戸口が求める 2 段を取りますが、その段が属する壁の層に残りが 1 段しかなければ 1 段で止まります — 1 段だけの層の下のドアは屋根を切り抜かず 1 段だけ開きます。開く段は石積みの層の中に入っていなければ なりません。届かない壁に対して書かれたドアは W_DEFERRED_MEMBER となり何も切り抜きません。同じ本体 の window が受け取るのと同じ指摘です (walls が塗る層は §9.3.5 が定めます)。

重要なメンバは id= を宣言でき、class= はメンバをグループ化します。id= を持たないメンバには、 親 / ロール / side / level / offset から導かれる安定した意味ベースのアドレスがコンパイラによって 割り当てられます (コンポーネント・編集・サイト §9.2)。

place 行は例外で、id= は必須です。省略すると E_INCOMPLETE_PLACE になります。自動アドレスは自分 のいるボディの外では何も指しませんが、place の id= は east_of= と connect が参照する名前であ り、その .nbt が書き出される名前でもあります。勝手に作った名前は、作者が書いてもいなければ指すこと もできない名前になってしまいます。