Nimのpragmaを自分で定義する

commits1 min read読了の目安(約1700字

はじめに

こんにちは、Neoと申します。
この記事では、Nimのユーザー定義pragmaについて解説します。

pragmaとは

Nimのpragmaについては、こちらの記事が詳しく解説してくれています。

pragmaの仕組み

pragmaは、macro/templateか、{.pragma.}pragmaを使って定義できます。

template myPragma(symbol: string; val: untyped): untyped =
  discard

let a {.myPragma("a").} = 1

この例では、myPragmaというtemplateを定義して変数aにpragmaとして使用しています。そして、次のようなコードと同じように解釈されます。

myPragma("a"):
  let a = 1

また、{.pragma.}プラグマを使うと、複数のpragmaをまとめて1つのプラグマにしたり、custom pragmaを定義できたりします。

when majorVersion >= 1:
  {.pragma: deprOrErr, error.}
else:
  {.pragma: deprOrErr, deprecated.}

この例では、majorVersionという変数が1以上の時はerror、それ以外ならdeprecatedと同じ効果を表すdeprOrErrpragmaを定義しています。
カンマで区切ってさらに他のpragmaも追加すれば、2つ以上のpragmaを1つのpragmaにまとめられます。

custom pragma

{.pragma.}pragmaをつけたtemplateで、custom pragmaを定義できます。

template sample1 {.pragma.}
template sample2(name = "") {.pragma.}
template sample3(title: string, t: typedesc) {.pragma.}

定義したcustom pragmaとその引数は、macrosモジュールのhasCustomPragma,getCustomPragmaValなどで検知できるので、macroのためにつける注釈として便利です。

自分で定義してみる

macroを使って、渡されたプロシージャの名前の末尾に"Cmd"を追加し、戻り値の型をint型にするcmdpragmaを書いてみました。

import macros

macro cmd(theProc: untyped): untyped =
  result = theProc
  result.name = newIdentNode(theProc.name.strVal&"Cmd")
  result[3][0] = newIdentNode("int")

proc cd(dir = "~") {.cmd.} =
  # do something
  result = 0

cdCmd() # OK
cd() # Error

cdというプロシージャを定義していますが、cmdpragmaによって名前が書き換えられているため、cdではなくcdCmdという識別子で呼び出す必要があります。また、戻り値の型がint型に書き換えられています。

おわりに

macroやtemplateの知識が必要なので、少しだけハードルが高いかもしれませんが、使いこなせれば強力だと思いました。
最後までお読みいただきありがとうございました!

参考

Nim Manual User-defined pragmas