概要
- デバッグビルド時のみ変数をダンプするNimのマクロを定義して、ヒャッハーします。
動機
以前書いた記事VSCodeでNimのデバッグ(ステップ実行)により、Nimのプログラムコードをステップ実行できるようになったは良いのですが、いざブレークポイントに止まって、ローカル変数を見ようとしても、値が参照できないというというトホホな状況があります。
args
を見ようとしても、左のペインで内容を確認できない。。。
結局のところ
echo
を使って変数をコンソールに表示させるようなコードを、ゴニョゴニョ書くことになります。
echo "args =" & $args
とはいえ、デバッグが終われば、先のコードは削除するか、コメントにするみたいな対応をしないといけないです。
when defined(debug):
C/C++のifdefディレクティブのように、nimのコンパイル時に-d:debug
を指定することにより、ソースコード上でdebugかどうかの判断ができます。
そのため、先のechoのコードを以下のように直すことにより、-d:debug
でコンパイルした時にのみecho
文が動作することになります。
when defined(debug):
echo "args =" & $echo
ただし、この方法でソースのいろんな箇所にwhen ~ echo
のブロックを埋め込んでいくのは、可読性の低下も考慮すると、少々ためらわれます。
それマクロで
- デバッグビルド時にのみ
echo
で変数のダンプを行うNimのマクロを作成し、シンプルに変数をダンプできるようにします。 - 楽をしたいので、マクロの中で変数名を取得できる機能を使って、
変数名 => 値
という形式で出力してしまいます。
debugマクロ利用例
import debug
proc sample(x: int, y: string): int =
result = x + 1
echo "start"
let a = 123
let bbbb = "ABCD"
let cc = sample(a, bbbb)
# 変数をダンプ
debug(
"parameters", a, bbbb,
"result", cc)
結果
[debug] ---------------------------
[debug] - - - - - - - - - - - - - -
[debug] parameters
[debug] - - - - - - - - - - - - - -
[debug] a => 123
[debug] bbbb => ABCD
[debug] - - - - - - - - - - - - - -
[debug] result
[debug] - - - - - - - - - - - - - -
[debug] cc => 124
[debug] ---------------------------
マクロの仕様
-
debug
に渡せるのは、文字列リテラルか変数のみで複数の指定が可能。- 関数呼び出し等のステートメントを渡すことはできません。
-
debug func(1,2)
とかはダメ。
- 変数を渡した場合、
変数名 => 値
の形式で出力 - 変数名は右揃えで表示
- 文字列リテラルを渡した場合には、その文字列のみを出力
- ダンプできる変数の型が**
$
関数**を実装していること -
/dev/tty
が利用できるならそちらへ出力するため、ダンプした内容はリダイレクトされません
マクロはこちら
debug.nim
import macros
import strformat
import strutils
macro debug*(n: varargs[untyped]): untyped =
var buff = fmt"""
when defined(debug):
block:
var useTty = false
let tty =
try:
let term = open("/dev/tty", fmWrite)
useTty = true
term
except IOError:
stdout
defer:
if useTty:
tty.close
tty.writeLine ("[debug] ---------------------------")
"""
# get max variable length
var maxLen = 0
for x in n:
if x.kind == nnkIdent:
let l = ($x).len
if l > maxLen:
maxLen = l
# dump variable
for x in n:
# echo x.kind
if x.kind == nnkStrLit:
buff = buff & fmt"""
tty.writeLine ("[debug] - - - - - - - - - - - - - -")
tty.writeLine ("[debug] " & {repr(x)})
tty.writeLine ("[debug] - - - - - - - - - - - - - -")
"""
if x.kind == nnkIdent:
let name = " ".repeat(maxLen - ($x).len) & $x
buff = buff & fmt"""
tty.writeLine ("[debug] {name} => " & ${repr(x)})
# tty.writeLine ("[debug] ---------------------------")
"""
buff = buff & """
tty.writeLine ("[debug] ---------------------------")
"""
# echo buff
parseStmt(buff)
macro if_debug*(head: untyped, body: untyped): untyped =
let bodies = repr(body).replace("\x0a", "\x0a ")
let stmt = fmt"""
when defined(debug):
block:
let stage = {repr(head)}
debug stage{bodies}
"""
# echo stmt
result = parseStmt(stmt)
when isMainModule:
proc sample(x: int, y: string): int =
result = x + 1
echo "start"
let a = 123
let bbbb = "ヒャッハー"
let cc = sample(a, bbbb)
debug(
"parameters", a, bbbb,
"result", cc)
# debug block
if_debug("step 1"):
for x in 1..3:
debug("loop", x)
if_debug("step 2"):
debug "this is debug"
if_debug("step 3"):
debug "this is debug"
利用方法
-
debug.nim
を開発中のソースがあるフォルダに作成し上記のソースをコピペ - 利用したいソースで、
import debug
を追加する -
debug
メソッドに、ローカル/グローバル変数を渡す - nimのコンパイラで、
-d:debug
を指定してコンパイル&実行
sample.nim
import debug
let a = 1
let b = "ヒャッハー"
debug a, b
# コンパイル&実行
nim c -r -d:debug sample.nim
[debug] ---------------------------
[debug] a => 123
[debug] b => ヒャッハー
[debug] ---------------------------
-
debug a, b
は以下のように展開されています。
when defined(debug):
block:
var useTty = false
let tty =
try:
let term = open("/dev/tty", fmWrite)
useTty = true
term
except IOError:
stdout
defer:
if useTty:
tty.close
tty.writeLine ("[debug] ---------------------------")
tty.writeLine ("[debug] a => " & $a)
tty.writeLine ("[debug] b => " & $b)
tty.writeLine ("[debug] ---------------------------")
-
/dev/tty
を使うアイデアは、@jiro4989さんの端末のパイプ先に特定の出力だけ渡す方法を参考にさせていただきました。 - ttyとstdoutの切り替えを行っているのは、
gdb
を利用したデバッグを行っている時にtty
が使えない制限があったためです。
マクロコワクナイヨ
- 今回作成したマクロは、展開したいNimのコードを組み立てた文字列をparseStmtに渡すことにより、マクロの展開を行っています。
- Nimとして展開したいコードを生成すればよいので、この方法だと容易にマクロが定義できるんだなと思いました。
- マクロ自体のデバッグは、マクロ内で
echo
することで、マクロの実行における動作確認ができます。 - マクロを作成するのにあたり、@gmShibaさんの実践Nimマクロを参考にさせてもらいました。
まとめ
-
debug
マクロを使うことにより、変数の内容の確認が気軽にできるので、開発時の御供にしていただければ幸いです。 - 今回のマクロは拙作のnimのパッケージマネージャnimbleによるプロジェクトテンプレートにも組み込んでいます。