Elixir OSSハック②【Livebook②】：Livebookコードブロックの実行付近を特定し、改造

この記事は、[Elixir Advent Calendar 2023 シリーズ12](https://qiita.com/advent-calendar/2023/elixir) の20日目です

---

[piacere](https://twitter.com/piacere_ex) です、ご覧いただいてありがとございます :bow:

今回も、[前回](https://qiita.com/piacerex/items/8577f0e9a91556feb97c)に引き続き、下記の追体験 … つまり「Livebookの改造」をするコラムで、今回は、Elixirコードを実行できる「Livebookコードブロック」についてハックしていきます

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">たった1時間の短時間で、<br><br>・Livebookホーム画面<br>・ユーザー設定<br>・セッション（起動されたNotebookのこと）<br>・コードエディタ部分<br>・コード実行部分<br><br>のハックや改造ができました😆 <a href="https://twitter.com/hashtag/LiveViewJP?src=hash&amp;ref_src=twsrc%5Etfw">#LiveViewJP</a><br><br>今回、初めてLivebookハックしてみましたが、いつものElixirらしく読みやすいコードでハッピーでした😍 <a href="https://t.co/EhohlbKTmA">pic.twitter.com/EhohlbKTmA</a></p>&mdash; piacere @ Elixir／D2C／xR＋メイドコスプレでデジタルから現実改変する49歳 (@piacere_ex) <a href="https://twitter.com/piacere_ex/status/1706658075321704577?ref_src=twsrc%5Etfw">September 26, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

みんなでコードを眺めて、震えて、納涼するイベント、[LiveViewJP#21「Livebookコードを眺めてみんなで震えて納涼する会」](https://liveviewjp.connpass.com/event/294500/) のコラム版です

https://liveviewjp.connpass.com/event/294500/

ちな、私もマトモにLivebookコードを読んだことが無かったので、会が成立しない位、グダグダになったらゴメンナサイ … と予め謝った上で開催しました

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">来月の <a href="https://twitter.com/hashtag/LiveViewJP?src=hash&amp;ref_src=twsrc%5Etfw">#LiveViewJP</a> は、Elixirに素晴らしいWeb上開発体験を提供している「Livebook」のコードハック会😜<a href="https://t.co/tlHSq67Wmg">https://t.co/tlHSq67Wmg</a><br><br>みんなでコードを眺めて、震えて、納涼しましょう🤣<br><br>ちな私もマトモに読んだこと無いので、会が成立しない位、グダグダになったらゴメンナサイ…と予め謝っておく😋</p>&mdash; piacere @ Elixir／D2C／xR＋メイドコスプレでデジタルから現実改変する49歳 (@piacere_ex) <a href="https://twitter.com/piacere_ex/status/1694957213415333990?ref_src=twsrc%5Etfw">August 25, 2023</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

結果的には、新たなLiveViewデバッグ機能も使い倒す、ステキなElixir printfデバッグ（正確にはIO.inspectだがｗ）のシェア会になりましたので、その一端をコラムでも共有します 😋
あと、このコラムが、面白かったり、役に立ったら、![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/907a8695-4d83-1b5c-8052-d0ce52de5f66.png) をお願いします :bow:

# Elixirコードを実行できる「Livebookコードブロック」

実は、Elixir標準モジュールの仕様を知っていると、ここはクイックハックできます

`Code` モジュールです
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/e9167ca4-5413-7fef-91c1-7bbd26eba86f.png)](https://hexdocs.pm/elixir/Code.html)

https://hexdocs.pm/elixir/Code.html

`Code` モジュールには、下記のようなElixirコードを実行し、その結果を返す関数があるので、これで当たりを付けていきます

- `Code.eval_file`
- `Code.eval_string`
- `eval_quoted`
- `eval_quoted_with_env`

`Code.eval_file` はハズレのようです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/34de812f-76e5-ec35-1452-3a219b226c24.png)

`Code.eval_string` もハズレです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/4d98951c-7caa-07e9-49d5-4e6679f7bc80.png)

お、`eval_quoted_with_env` 付近が何か匂います … 関数も `eval` ですし
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/391c183e-ca74-36a0-49d9-818fb909f260.png)

printfデバッグを仕掛けて、合ってるか確認してみましょう

```diff_elixir:lib/livebook/runtime/evaluator.ex
…
  defp eval(:elixir, code, binding, env) do
+IO.puts "=========== eval ==========="
+IO.inspect code
+IO.puts "=========== eval ==========="
    {{result, extra_diagnostics}, diagnostics} =
      Code.with_diagnostics([log: true], fn ->
        try do
…
```

Livebookからコード実行してみると … ビンゴッ！！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/40cbfafb-2f39-57bd-94b1-fdd2148b654e.png)

Livebookのこの箇所に `IO.puts` や `IO.inspect` を仕掛けると、Livebook上で表示されるのですね :yum: 

# Livebookコードブロックの情報を取得する

コードブロックの実行部分が特定できたので、何か改造を施してみましょう

その前に、このコード実行評価でどのような値を返却しているのか見てみます

```diff_elixir:lib/livebook/runtime/evaluator.ex
…
  defp eval(:elixir, code, binding, env) do
…
        |> case do
          {:ok, value, binding, env} ->
+IO.puts "=========== eval ==========="
+IO.inspect value
+IO.inspect binding
+IO.inspect env
+IO.puts "=========== eval ==========="
            {{:ok, value, binding, env}, []}
…
```

なるほど、実行結果と、謎の `binding`、そしてコンパイル時の実行環境情報を保持する `Macro.Env` が返されているようです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/56cb0c84-4e2d-2130-5a59-fa8832a490d0.png)

`Macro.Env` は、下記の通りです
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/e9b8c2b7-10b2-8d94-026e-0a23ea3bd2d1.png)](https://hexdocs.pm/elixir/Macro.Env.html)

`env` から、下記のようにコードのロケーション情報を拾ってみましょう

```diff_elixir:lib/livebook/runtime/evaluator.ex
…
  defp eval(:elixir, code, binding, env) do
…
        |> case do
          {:ok, value, binding, env} ->
IO.puts "=========== eval ==========="
IO.inspect value
IO.inspect binding
+IO.inspect env |> Macro.Env.location
IO.puts "=========== eval ==========="
            {{:ok, value, binding, env}, []}
…
```

セルの情報が拾えそうです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/75fd8103-0b37-3993-1d65-93efd6374815.png)

`value` がリストのときは、セル情報を載せて、返してみましょう

```diff_elixir:lib/livebook/runtime/evaluator.ex
…
  defp eval(:elixir, code, binding, env) do
…
        |> case do
          {:ok, value, binding, env} ->
-IO.puts "=========== eval ==========="
-IO.inspect value
-IO.inspect binding
-IO.inspect env |> Macro.Env.location
-IO.puts "=========== eval ==========="
-           {{:ok, value, binding, env}, []}
+           case value do
+             value when is_list(value) -> {{:ok, value ++ "location: #{Keyword.get(env |> Macro.Env.location, :file)}", binding, env}, []}
+             _ -> {{:ok, value, binding, env}, []}
+           end
…
```

実行結果に、セル情報を入れることが出来ました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/4a66972f-89cf-fe88-c630-ce74f01332be.png)

# 終わりに

Livebookコードブロックのハックをしましたが、簡単でしたよね？

Elixirの知識があれば、OSSをハックしやすいということが伝われば幸いです

次回は、Livebookをノーコードたらしめる重要機能である「SmartCell」付近を攻略していきます

# p.s.このコラムが、面白かったり、役に立ったら…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/907a8695-4d83-1b5c-8052-d0ce52de5f66.png) にて、どうぞ応援よろしくお願いします :bow: