LuaでLuaデバッガのLUPEを作りました #lua


## Luaとデバッグ
みなさんLuaを使ったことはありますか？
私は元々JavaScriptを書くことが多かったのですが，最近はよくLuaのコードを書いています。JavaScriptと比べて，私はLuaの方が好きです。`metatable`やコルーチンは魅力的だし，後述する`debug`ライブラリも強力です。しかし，実際の開発を行うとなると，言語仕様や標準ライブラリだけではなくその周辺のツールも重要になってきます。

JavaScriptでWebアプリを開発する際，多くの方がChromeのDev Toolを使っているでしょう。
Dev Toolを使えば，強力なJavaScriptのデバッガやプロファイラを使うことができます。
また，Chrome Extentionを使えばさらに，通信の内容をデバッグ用に変更したりと，さまざまなことができます。

一方，Luaではどうでしょう？一応，ちらほらデバッグツールを見かけますが，環境に依存していたり，機能が貧弱だったりします。Luaの場合，通常，単体で使うのではなくホスト環境があり，一部の機能をスクリプティングするために仕様することが多いでしょう。そのため，環境に依存したり，特定のライブラリに依存していると非常に使いづらいかと思います。

そこで，夏休みにピュアLuaでLUPEという名前のLuaのデバッガを作りました。そして，先日公開することができたため，この記事ではLUPEの機能と仕組みを説明したいと思います。

## LUPEの機能

LUPEは以下のリポジトリで公開しています。

* https://github.com/tenntenn/lupe

LUPEが提供する主な機能は以下のとおりです。

* ブレークポイント（追加・削除・一覧表示）
* ステップ実行（ステップイン・ステップアウト・ステップオーバー）
* 現在行周辺のコードの表示
* ローカル変数一覧の表示
* 式（チャンク）の評価
* ローカル変数とグローバル変数の宣言位置の推測
* ウォッチ式
* 簡易プロファイラ
* コルーチン上でのデバッグ

LUPEはすべてLuaで書かれており，標準ライブラリのみを使用しています。そのため，ほとんどホスト環境に影響されずに，標準入出力が使える環境であれば使用できます。また，標準入出力が使えない場合も入出力方法を変えることで，デバッグすることは可能です。

細かい使い方は[README](https://github.com/tenntenn/lupe/blob/master/README.md)を読んでもらうとして，ここでは基本的な使い方を説明します。

まず，`lupe.lua`をいずれかの方法で読み込みます。通常は`require`を使うと思いますが，ホスト環境が用意する関数にluaファイルを読み込む機能があればそれを使用することも可能です。`lupe.lua`を読み込むと，`Lupe`というオブジェクトがグローバル変数として定義されるので，そのオブジェクト`start`というメソッドを呼びましょう。`start`メソッドは後述する`debug.sethook`を使って，処理の途中で止まれるようにフックを仕込みます。

```lua
require('lupe')
Lupe:start()
```

つぎに，処理を止めたい箇所で`Lupe()`を呼び出します。

```lua
function hoge()
  -- some code
  Lupe() -- ここで止まる
end
```

`Lupe()`は，実行した行で処理を中断し，デバッグコマンドを受け付けるプロンプトを表示します。

```
LUPE>
```

使用できるデバッグコマンドは以下のとおりです。

| コメンド名   | 省略形 | 説明 |
|:-----------|:------------|:------------|
|addBreakPoint|ab|ブレークポイントの追加|
|removeBreakPoint|rb|ブレークポイントの削除|
|breakPointList|bl|ブレークポイントの一覧|
|step|s|ステップオーバー|
|stepIn|si|ステップイン|
|stepOut|so| ステップアウト|
|run||継続|
|list|l|ソースコードの表示|
|vars|v|ローカル変数の表示|
|definedLine|d|変数の宣言位置の推測|
|setWatch|sw|ウォッチ式の設定|
|removeWatch|rw| ウォッチ式の削除|
|watch|w|ウォッチ式の表示|
|上記以外||チャンクの評価|

それでは，以下のコードをデバッガで追ってみましょう。

```sample.lua
require("lupe")
Lupe:start()

local a = 100

function hoge()
  local b = 200
  Lupe()
  fuga()
end

function fuga()
  local c = 200
  print(c)
end

hoge()
```

luaコマンドで実行してみると，9行目で処理が止まります。

```
$ lua sample.lua
stop at @sample.lua:9
LUPE>l
   6: function hoge()
   7:   local b = 200
   8:   Lupe()
>  9:   fuga()
  10: end
  11:
  12: function fuga()
```

13行目にブレークポイントを仕掛けてみます。

```
LUPE>ab 13
LUPE>bl
@sample.lua:13
LUPE>l 4
   5:
   6: function hoge()
   7:   local b = 200
   8:   Lupe()
>  9:   fuga()
  10: end
  11:
  12: function fuga()
 *13:   local c = 200
```

そして，中断した処理を継続させます。

```
LUPE>run
stop at @sample.lua:13
LUPE>l
  10: end
  11:
  12: function fuga()
>*13:   local c = 200
  14:   print(c)
  15: end
  16:
```

1ステップ進めて，変数の一覧を表示してみます。

```
LUPE>s
stop at @sample.lua:14
LUPE>v
c(number): 200
```

変数に代入してみましょう。

```
LUPE>c=300
LUPE>run
300
```

`c`の中身がうまく変わっていますね。

## debugライブラリ

Luaには[`debug`ライブラリ](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#6.10)という強力なデバッグ用の強力な標準ライブラリが存在します。このライブラリを使えば，行や関数呼び出しごとにフック関数を仕掛けたり，現在の行で宣言されているローカル変数等を取得することができます。LUPEではその機能を使って，デバッガの機能を提供しています。

LUPEで使用している`debug`ライブラリの機能について簡単に説明します。

### debug.sethook

[`debug.sethook`](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#pdf-debug.sethook)は，行，関数の呼び出し，関数からリターンごとに呼び出されるフックを仕掛けることができます。LUPEでは，この機能を使ってデバッグ用のコールスタックに，関数呼び出しごとのコンテキストを記録しています。変数の状態などは，行ごとに更新され，関数からリターンされるとその情報を破棄します。

ブレークポイントは行ごとに，その行にブレークポイントがあるかどうか判断して，ブレークポイントがあればその行で処理を止めます。ステップ実行は，ステップ実行を行った時のコンテキストを記録しておき，行ごとにその行で止まるべきか判断させます。たとえば，ステップアウトの場合は，ステップアウトを実行した場所より，上の階層になった場合に止まるようにコールスタックの状態を見て判断しています。

### debug.getlocal, debug.setlocal

[`debug.getlocal`](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#pdf-debug.getlocal)を使うと，その行から相対的に指定した階層のローカル変数を取得できます。[`debug.setlocal`](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#pdf-debug.setlocal)を使うとその逆ができます。この2つの関数を使って，ローカル変数の一覧を取得し，チャンクの評価で変数の内容が変わったときに変更させています。同様にクロージャで，上位値の取得・変更をする場合は，[`debug.getupvalue`](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#pdf-debug.getupvalue)と[`debug.setupvalue`](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#pdf-debug.setupvalue)を使います。


### チャンクの評価

Lua5.2では[`load`](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#pdf-load)に文字列を渡すとそれをLuaのチャンクとして評価します。環境を指定しない場合は，グローバル環境`_ENV`と同様の環境で評価されます。ローカル変数を反映させるには，自分で環境を作り，引数に渡します。

また，Lua5.1では[`load`](http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html#pdf-load)はファイルを読み込むことしかできないので，代わりに[`loadstring`](http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html#pdf-loadstring)を使います。`loadstring`は環境を指定することができないので，[`debug.setfenv`](http://milkpot.sakura.ne.jp/lua/lua51_manual_ja.html#pdf-debug.setfenv)を使って，事前に環境を設定する必要があります。

### collectgarbage

[`collectgarbage`](http://milkpot.sakura.ne.jp/lua/lua52_manual_ja.html#pdf-collectgarbage)は`debug`ライブラリではありませんが，非常に強力です。引数に`"stop"`を指定するとGCの動作を止めることができ，ゲームのコアなパートや重要な演出を表示しているときなど，GCを走らせたくないときに使えます。また，引数に`"count"`を指定すると，現在Luaの処理系で使用しているメモリの使用量（キロバイト）が取得できます。LUPEでは，この機能を使って関数のメモリの使用量を計測する簡易プロファイラを実装しています。

## まとめ
この記事では私が開発しているLUPEの紹介とそこで使われているLuaの強力な`debug`ライブラリの紹介を行いました。LUPEは現在開発中で，おそらくバグもいっぱいあると思います。しかしながら，ここまで機能を提供するピュアLuaで書かれたデバッガは他に無いかと思います。デバッガなので，開発の現場で使っても誰にも迷惑を書けることはありません。そのため，誰かの開発の役に立てればと思います。使ったよって報告がもらえるとテンションあがるので，お願いします。

まだ，公開はされていませんが，LUPEのWebフロントエンドを作っています。完成すれば，マウス使ってデバッグができるようになると思います。