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

記法形式の比較

「JSON か YAML でよかったのでは」は最初に来る問いである。新しい構文には値段がある — パーサ、エディタ支援、シンタックスハイライト、そして読み手が覚え直す手間。

それでも DSL にしたのは、設計基準を一つに固定したからである。

LLM が一つのコンテキストで読み切り、正しく部分編集できるか。

この基準から、テキストネイティブであること、参照が人間の読める階層パスであること、単一の巨大ファイルではなく小さなファイルの集合であること、単位と座標系を先頭で一度だけ宣言することが、すべて決まる。そして著者形式が DSL であることも、ここから出る。

なお koyu は機械形式も持っている — 正準 JSON である。二つの綴りが一つの意味論を共有しているというのがこの設計であって、JSON を拒んでいるわけではない (正準 JSON)。

同じ二室を書き比べる

同梱の two-rooms.muro から注釈を除いた本文はこれである。

koyu 1.0
name 二室
unit mm
grid X 0 3600 7200
grid Y 0 4500
level L1 0 h:2400 slab:150
space /L1/a room X1..X2 Y1..Y2 name:居室A daylight:1
space /L1/b room X2..X3 Y1..Y2 name:居室B daylight:1
space /out exterior name:外部
boundary /L1/a /L1/b t:120 spec:PW1
  door w:780 h:2000
boundary /L1/a /out t:150 spec:EW1 fire:60
  window w:2600 h:1100 edge:S name:腰窓
boundary /L1/b /out t:150 spec:EW1 fire:60
  door w:900 h:2100 edge:S name:玄関
  window w:2600 h:1100 edge:E name:腰窓

同じ内容を YAML で素直に書くとこうなる。

koyu: "1.0"
name: 二室
unit: mm
grid:
  X: [0, 3600, 7200]
  Y: [0, 4500]
levels:
  - name: L1
    z: 0
    h: 2400
    slab: 150
spaces:
  - path: /L1/a
    type: room
    region: {x: [X1, X2], y: [Y1, Y2]}
    name: 居室A
    daylight: 1
  - path: /L1/b
    type: room
    region: {x: [X2, X3], y: [Y1, Y2]}
    name: 居室B
    daylight: 1
  - path: /out
    type: exterior
    name: 外部
boundaries:
  - a: /L1/a
    b: /L1/b
    t: 120
    spec: PW1
    openings:
      - kind: door
        w: 780
        h: 2000
  # …残り二つの境界と三つの開口が続く

o200k_base で実測した数字である。

形式バイトトークン対 DSL
DSL (本文)496162201.0x
YAML (同じ内容)947614141.9x
正準 JSON (機械形式)2,1641407293.3x

行が 3.8 倍、トークンが 1.9 倍になる。そして増えた分は建築の情報ではない — path: type: region: openings: kind: といった、構造を綴るためだけのキーである。

一行が一つの決定である

DSL の側で決定的なのは、トークン数より構造の見え方である。

space /L1/a room X1..X2 Y1..Y2 name:居室A daylight:1

一行が一つの空間であり、一つの設計判断である。位置引数の並び (パス、型、X 範囲、Y 範囲) は建築の言い方の順そのもので、属性は後ろに続く。

字下げが包含を表す。

boundary /L1/b /out t:150 spec:EW1 fire:60
  door w:900 h:2100 edge:S name:玄関
  window w:2600 h:1100 edge:E name:腰窓

開口が境界に属することは、openings: というキーではなく、字下げが言う。関係と、その上に置かれた区間、という構造がそのまま字面になる。

診断が行を指せる

これは実務上いちばん効いている。

✖ b3.muro:line 7: Duplicate boundary: /L1/a | /L1/b (first seen at b3.muro:line 6)

エラーが「ファイルと行」を指す。書いた人は、その行を見れば直せる。エージェントも同じで、行番号を受け取ってその行だけを書き換えられる。

YAML でも行番号は取れるが、指せる粒度が違う。一つの空間が 6 行に散っていれば、「この空間が悪い」は 6 行のどこかを指すことになる。一行一決定なら、診断の粒度と決定の粒度が一致する。

差分が構成の言葉になる

通り芯を一本動かしてみる。

npx tsx src/cli.ts diff tr-a.muro tr-b.muro
± grid X X2 3600 → 4200
± /L1/a: area 16.20 m2 → 18.90 m2
± /L1/b: area 16.20 m2 → 13.50 m2

「X2 が動いて、a が広がり、b が縮んだ」と読める。座標の羅列ではない。

これは DSL であることの直接の効果ではないが、位置を通り参照で書くという決定と組みになっている。生の座標を書ける形式では、通り芯を一本動かすと数十の座標が一斉に変わり、差分は読めなくなる。

位置は言葉で書く

space /L1/a room X1..X2 Y1..Y2
  door w:900 at:X2+450 edge:S

X2+450 は「通り芯 X2 から 450mm」である。座標ではなく、決定の言い方である。

X1..X2 も同じで、「X1 通りから X2 通りまで」と読む。通り芯が動けば領域が追随し、原本は一文字も変わらない。

汎用形式でこれを書けないわけではない。"region": {"x": ["X1", "X2"]} と書けばよい。ただしその瞬間、それは汎用形式の中に埋め込まれた DSL である。文字列の中に独自の文法を持たせるくらいなら、文法を表に出したほうが読める。

繰り返しを畳む語がある

大きい建物は、違うものが多いのではなく同じものが多い。DSL はその性質に合わせた語を持てる。

level L4..L10 pitch:3000
space /L2..L9/A/ldk ldk X1..X3 Y1..Y2
band X X2..X4 Y4-3200..Y5
  space /L14..L19/s01 room w:5000 name:スイート01
  space /L14..L19/s02 room w:5000 name:スイート02
  space /L14..L19/s03 room w:rest name:スイート03

複合建築の実例では、コアの 21 レベル分が 9 行、ホテル 78 室が 13 行の帯の宣言から展開されている (IFC・USD との比較)。

汎用形式でこれをやろうとすれば、アンカーとエイリアス、あるいは外部のテンプレート機構が要る。そしてそこで畳まれた結果は、もう YAML パーサだけでは読めない。

入場基準は明確である — muro に入るのは決定的な展開だけ。選択も条件分岐も含まないものだけが入る。「廊下は 1.8m、残りを住戸で等分」は決定的なので入る。「住戸をなるべく南に」は最適化なので入らない — それはエージェントが解いて、解を muro に書く。数式は入るがマクロは入らない。

機械形式は別にある

著者形式と機械形式を分けているので、DSL であることの代償は小さい。

著者形式 (.muro)機械形式 (正準 JSON)
誰が読む人と LLMプログラム
何を持つ書かれた構成合成の結果
言語の版綴りの版
安定性書き方は自由同じ入力からは同じバイト列

外部のツールが koyu を読みたければ、正準 JSON を読めばよい。独自の文法を実装する必要はない。

代償

正直に書いておく。

  • パーサを自分で書くことになる。koyu は実行時依存ゼロなので、これは自前である
  • エディタ支援を自分で用意することになる。シンタックスハイライトの文法定義を一つ持ち、実装と台帳との一致をテストが縛っている
  • 読み手が構文を覚える必要がある。ただし覚える量は小さい — 非字下げの行が 16 種、字下げの行が 9 種で、入れ子も括弧も終端子も無い (.muro の全構文)
  • 汎用ツール (YAML の linter やスキーマ検証) が使えない。代わりに check が 65 の診断を持つ

この代償を払う価値があるかは、設計基準に照らして判断されている。一棟が一つのコンテキストに載り、エージェントが行単位で編集でき、差分が構成の言葉で読める — その三つが同時に成り立つなら、払う値打ちがある、というのがこの選択である。

この先