IMP-Doc のすゝめ

この記事は [Minecraft Command Advent Calendar 2023](https://qiita.com/advent-calendar/2023/mccmd) 1 日目の記事です。

## はじめに
普段 function を書く際に ｢function が何を要求していて何をするのか分からない｣ や ｢間違った function を呼んでしまった｣ という経験はないでしょうか？
そのような問題を解決する為に定義され、運用が試みられている IMP-Doc と言うドキュメント仕様があります。
この記事では IMP-Doc を利用することで上記の問題がどの様に解決されるのか、IMP-Doc をどう使うのかについて書き記したいと思います。

注意点として、IMP-Doc は Minecraft 公式の仕様ではなく、あくまで有志が制定して運用をするものであり Minecraft 内ではただのコメントとして扱われます。

次に、この記事内では [Visual Studio Code](https://code.visualstudio.com/) に [Data-pack Helper Plus](https://marketplace.visualstudio.com/items?itemName=SPGoding.datapack-language-server) (下記 DHP と略) を導入した環境を前提として記述する箇所があります。
上記環境以外でも IMP-Doc そのものの記述による理解の容易さ等は得られますが、直接的な開発支援を得ることが出来ないため上記環境を推奨します。

また、この記事で解説している内容は [Arcensoth/imp-spec](https://github.com/Arcensoth/imp-spec/blob/master/docs/imp_doc.md) 及び DHP の [Wiki](https://github.com/SpyglassMC/Spyglass/wiki/IMP-Doc) に記述されている内容を元にしています。
より詳しい情報や理解を得たい場合、上記情報を参照することを推奨します。


## TL;DR
アクセス修飾子について、[TheSkyBlessing](https://tsb.scriptarts.jp) の [wiki](https://github.com/ProjectTSB/TheSkyBlessing/wiki/convetion#21-imp-doc%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9) に御託はいいから使い方教えろって人向けの説明があるよ。


## 一部用語の定義
### リソース
コマンド内で開発者が定義するほぼ全てのもののこと。
function や tags/*、 advancement 等のファイルとして定義するものや、スコアボードオブジェクト、tag、スコアホルダー等が該当します。

### declare コメント
DHP の機能として提供されているリソースを定義出来る特殊なコメントのこと。
```mcfunction
#declare <リソース種別> <リソース名>
```
の構文で記述します。
ここで定義されたリソースは (存在する場合は) 他の手段で定義されたものと同様に扱われ、補完の一覧や参照の検索等に利用されます。

> 例: 
> ```mcfunction
> #declare tag/function hoge:fuga/piyo
> #declare tag init
> #declare score_holder #count
> ```


## IMP-Doc とは
下記に挙げる箇所に記述することで理解を容易に出来る特定の書式のコメントのことです。[^1]
- function の一行目 (通称 function ヘッダー)
- `scoreboard objectives add` や `team add` 等のコマンドを利用したリソース定義の前の行
- declare コメントを利用したリソース定義の前の行

ちなみに、IMP-Doc とは **I**nteroperable **M**odule **P**rotocol **Doc**umentation の略で、直訳すると ｢相互運用可能なモジュールのプロトコルドキュメント｣ になります。

[^1]: imp-spec として定められている IMP-Doc は正確には function ヘッダーに付与するもののみですが、便宜上 DHP で利用できるリソース定義も IMP-Doc の一種として扱います。これは [Arcensoth/imp-spec#1](https://github.com/Arcensoth/imp-spec/issues/1) で標準化の議論が行われているものです。(ここ二年以上動きがないので、どうにかする必要がありそうですが。)


## どうやって使うの？
### function に対して記述する IMP-Doc
function の先頭行に、次のような構造で記述します。
```mcfunction
#> <function 名>
```
```mcfunction
#> <function 名>
# <アノテーション>
```
```mcfunction
#> <function 名>
#
# <function の説明>
# <function の説明>
#
# <アノテーション>
```
`<function 名>` にはその function のリソースパスを入れてください。
`<function の説明>` は複数行に渡って書くことも可能です。あまり推奨はされませんが省略することも出来ます。
また、DHP では IMP-Doc の文章部分を Markdown として表示します。[^2]

[^2]: DHP v3.4.18 時点ではバグによって関数呼び出し元からのホバー時の表示が無くなっているため、表示出来るタイミングは限られています。かなしい。

### リソース定義に対して記述する IMP-Doc
リソース定義のコマンド ･ declare コメントの手前の行に、次のような構造で記述します。
```mcfunction
#> <リソースの簡易的な説明>
# <アノテーション>
<リソース定義>
```
```mcfunction
#> <リソースの簡易的な説明>
#
# <リソースの説明>
#
# <アノテーション>
<リソース定義>
```
```mcfunction
#> <リソースの簡易的な説明>
# <アノテーション>
    <リソース定義>
    <リソース定義>
```
`<リソース定義>` には `scoreboard objectives add` や `team add` や declare コメント等のリソースを記述します。
複数のリソース定義を一つの IMP-Doc に紐付ける場合、リソース定義の前にインデントがある必要があります。

### アノテーション
アノテーションは基本的に function に対して付与する特定の情報を定義するものです。
現状、IMP-Doc で定義されるアノテーションは `@user`, `@context`, `@input`, `@output`, `@reads`, `@writes`, `@patch`, `@handles`, `@public`, `@api`, `@within`, `@private` の 12 個で、追加で DHP が `@override`, `@deprecated`, `@internal` の 3 個を定義しています。
ただし、殆どのアノテーションは DHP v3.4.18 時点では意味を持ちません。
ここでは、私的によく使う二種類のアノテーションに絞って使い方を説明したいと思います。
下記以外のアノテーションについて知りたい方は [Arcensoth/imp-spec](https://github.com/Arcensoth/imp-spec/blob/master/docs/imp_doc.md#function-annotations) を読むのがおすすめです。

#### `@input` / `@output`
function の実行前に設定する必要のある値と、実行後に設定されている値を定義するアノテーションです。
このアノテーションも DHP v3.4.18 時点では特別な意味は持ちません。
ですが、入出力の明示は function のドキュメンテーションにおいて非常に重要なため、書いておいて損はないと思います。

`@input` / `@output` アノテーションは下記の様に記述します。
(例では `@input` を記述していますが、`@output` も同様です。)
```mcfunction
#> <function 名>
#
# <function の説明>
#
# @input storage <名前空間> <NBTPath>: <型>
#   <説明>
```
```mcfunction
#> <function 名>
#
# <function の説明>
#
# @input
#   score <スコアホルダー> <スコアボードオブジェクト>
#       <説明>
#   storage <名前空間>
#       <NBTPath>: <型>
#           <説明>
```

#### `@public` / `@api` / `@within` / `@private` / `@internal`
function ･ リソースがどこから参照されることを想定しているかを定義するアノテーションです。
このアノテーションは DHP v3.4.18 時点でも可視性を制限するために利用することが出来ます。
このアノテーションは ｢**アクセス修飾子 / Access Modifier**｣ とも呼ばれることがあります。
可視性とは、想定した参照元以外でそのリソースの種別の補完を表示した際にそのリソースを表示しないというものです。

例えば、特定の箇所でしか使わないスコアボードオブジェクトや function の定義が補完の一覧に出てきて邪魔だと思ったことはないでしょうか？
そのような状況の時、邪魔になるようなリソースに可視性を制限するアノテーションが付与されていれば表示されなくなります。

それぞれのアノテーションの意味は下記の通りです。
| アノテーション | 参照できる範囲               |
| :------------- | :--------------------------- |
| `@public`      | 全てのリソースから           |
| `@api`         | 全てのリソースから[^3]       |
| `@within`      | 任意のリソースから (後述)    |
| `@private`     | 参照不可                     |
| `@internal`    | 名前空間が同一のリソースから |

[^3]: `@public` と `@api` は基本的には同じようなものです。違いが気になる方は [Arcensoth/imp-spec](https://github.com/Arcensoth/imp-spec/blob/master/docs/imp_doc.md#function-annotation-api) を読むと良いです。

`@within` 以外のアノテーションは後ろに文字列を必要としないアノテーションです。下記の様に記述します。
```mcfunction
#> <function 名>
#
# <function の説明>
#
# @public
```

##### `@within` について
`@within` アノテーションはそのリソースへの参照を名前空間 ID パターンに一致する特定のファイルに制限することが出来るアノテーションです。
特定の function からしか呼び出さない function を定義したり、特定のディレクトリ内でのみ利用できるスコアボードオブジェクトを定義したりすることが出来ます。

`@within` アノテーションは `@within <ファイルの種類> <パス>` という構文で記載します。
指定できるファイルの種類は [DHP の Wiki](https://github.com/ChenCMD/datapack-helper-plus-JP/wiki/Cache-Type#file-types) を参照してください。
また、上記のもの他に全てのファイルの種類を受け付ける `*` を指定することも出来ます。
ただし、ファイルの種類は省略することも可能です。その場合、`*` として扱われます。

パスには下記のワイルドカードを使用することが可能です。
| ワイルドカード | 説明                                | 正規表現での許容文字列範囲 |
| :------------: | :---------------------------------- | :------------------------- |
|      `?`       | 任意の一文字                        | `[a-z0-9_-.]`              |
|      `*`       | `/`, `:` を含まない任意の文字列     | `[a-z0-9_-.]*`             |
|      `**`      | `/`, `:` を含んでも良い任意の文字列 | `[a-z0-9_-.:/]*`           |

`@within` アノテーションは具体的には下記の様に記述します。
```mcfunction
#> <function 名>
#
# <function の説明>
#
# @within example:hoge
```
```mcfunction
#> <function 名>
#
# <function の説明>
#
# @within * example:fuga/*
```
```mcfunction
#> <function 名>
#
# <function の説明>
#
# @within function example:piyo/*/piyo/**
```
```mcfunction
#> <function 名>
#
# <function の説明>
#
# @within
#   function example:meow/**
#   tag/function minecraft:load
```


## 例
今まで説明された IMP-Doc を使うことで、例えば次のような何をするのかが一目瞭然な関数を書くことが出来ます。
```mcfunction
#> advent_calendar:summon_cats
#
# 猫を N 匹召喚します。
#
# @input
#   score %count Argument
#       召喚する猫の数
#   storage argument:
#       variant: string
#           召喚する猫の模様
# @api

#> Temporary Storage
# @within function advent_calendar:summon_cats*
#declare storage advent_calendar:temp

data modify storage advent_calendar:temp args.variant set from storage argument: variant
function advent_calendar:summon_cats_recursive.m with storage advent_calendar:temp args

```
```mcfunction
#> advent_calendar:summon_cats_recursive.m
#
# 再帰的にスコア分だけ猫を召喚します。
#
# @input
#   score %count Argument
#       召喚する猫の数
#   args
#       variant: string
#           召喚する猫の模様
# @within function
#   advent_calendar:summon_cats
#   advent_calendar:summon_cats_recursive.m

$summon cat ~ ~ ~ {variant:"$(variant)"}
scoreboard players remove %count Argument 1
$execute if score %count Argument 1.. run function advent_calendar:summon_cats_recursive.m {variant:"$(variant)"}

```

## まとめ
この記事では IMP-Doc と呼ばれる mcfunction のドキュメンテーションを行う上で非常に役立つ仕様とその使い方に関する話を書かせていただきました。
IMP-Doc は DHP を前提をする使い方ではあるものの、DHP との併用でただのドキュメンテーション以上の意味を持つものになっていると思います。
IMP-Doc 仕様そのものに未来があるかは少々不安な部分はあるものの、特に複数人で開発を行うプロジェクトなどでは利用することで絶対に良い結果になると思っています。
ここまで読んでいただいた方も、もし機会が、それこそ特に汎用的な関数を作る際等には、是非利用してみてはいかがでしょうか？

## 参考にさせていただいたサイト
- [SpyglassMC/Spyglass Wiki](https://github.com/SpyglassMC/Spyglass/wiki/IMP-Doc)
- [Arcensoth/imp-spec](https://github.com/Arcensoth/imp-spec/blob/master/docs/imp_doc.md)