7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Nimでマクロを利用して変数のダンプを行う

Last updated at Posted at 2019-11-12

概要

  • デバッグビルド時のみ変数をダンプするNimのマクロを定義して、ヒャッハーします。

動機

以前書いた記事VSCodeでNimのデバッグ(ステップ実行)により、Nimのプログラムコードをステップ実行できるようになったは良いのですが、いざブレークポイントに止まって、ローカル変数を見ようとしても、値が参照できないというというトホホな状況があります。

argsを見ようとしても、左のペインで内容を確認できない。。。
Screenshot from 2019-11-12 21-44-09.png

結局のところ

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] ---------------------------")

マクロコワクナイヨ

  • 今回作成したマクロは、展開したいNimのコードを組み立てた文字列をparseStmtに渡すことにより、マクロの展開を行っています。
  • Nimとして展開したいコードを生成すればよいので、この方法だと容易にマクロが定義できるんだなと思いました。
  • マクロ自体のデバッグは、マクロ内でechoすることで、マクロの実行における動作確認ができます。
  • マクロを作成するのにあたり、@gmShibaさんの実践Nimマクロを参考にさせてもらいました。

まとめ

7
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?