Emacs dynamic moduleでD言語くんを召喚する

Page content

D言語くん Advent Calendar 2021 12日目の記事です。

tl;dr

Emacs lispからD言語で書いたD言語くんを召喚した

https://github.com/ShigekiKarita/emacs-dman

emacs

Emacs dynamic module とは

C言語で書いたプラグイン(共有ライブラリ)をEmacs lispから呼ぶためのモジュール。C言語からもEmacs lispの関数を呼べたり便利。

ダイナミックEmacsモジュール(dynamic Emacs module: 動的Emacsモジュール)とは、Emacs Lispで記述されたパッケージのように、Emacs Lispプログラムで使用するための追加機能を提供する共有ライブラリーです

マニュアル: https://ayatakesi.github.io/lispref/26.2/html/Dynamic-Modules.html

D言語でC言語の共有ライブラリを書く

C言語用のライブラリを書く話はこの辺にまとまっている(雑)

ちなみにEmacsが提供する emacs-module.h というファイルは dpp に変換してもらいました。まだ ImportC では読めなかった。

全体としては100行もないコードなので読んでほしい。おおまかにはこんな感じでD言語くんが時間を教えてくれる dman_time というネイティブ関数を、 emacs_module_initdman-time という名前で登録:

import emacs_module;
import core.runtime : Runtime;
import std.format : format;
import std.datetime.systime : Clock;

enum string dman = `
 o       o
 |        \
  \ ___   /
   |,-oo\\
   ||    || < %s
   ||    ||
   ||   //
   ||__//
   '---
    \  \
    /  /
    ^  ^
`;

extern (C):

int plugin_is_GPL_compatible;

emacs_value dman_time(
    emacs_env* env, ptrdiff_t nargs, emacs_value* args, void* data) {
  emacs_value func = env.intern(env, "message");
  emacs_value[1] args;
  string hello = format!dman(Clock.currTime());
  args[0] = env.make_string(env, hello.ptr, hello.length);
  env.funcall(env, func, args.length, args.ptr);
  return env.intern(env, "t");
}

int emacs_module_init(emacs_runtime* ert) {
  // Validate Emacs runtime and environment.
  if (ert.size < emacs_runtime.sizeof)
    return 1;
  emacs_env* env = ert.get_environment(ert);
  if (env.size < emacs_env.sizeof)
    return 2;

  // Initialize D runtime.
  Runtime.initialize();

  // Register functions.
  emacs_value[2] fn_sym_pair;
  fn_sym_pair[0] = env.intern(env, "dman-time");
  fn_sym_pair[1] = env.make_function(
      env, /*min_arity=*/0, /*max_arity=*/0,
      /*function=*/&dman_time,
      /*documentation=*/"Dman tells you current time.",
      /*data=*/null);
  env.funcall(env, env.intern(env, "defalias"),
              fn_sym_pair.length, fn_sym_pair.ptr);
  return 0;
}

そして共有ライブラリをコンパイル

dmd -shared dman.d -of=dman.so

すると Emacs の scratch とか適当な場所で elisp を書いて

(add-to-list 'load-path "path-to-dir-of-dman.so")
(load "dman")
(dman-time)

eval-buffer とかで評価すると、Message バッファにD言語くんが召喚されて時間を教えてくれます

 o       o
 |        \
  \ ___   /
   |,-oo\\
   ||    || < 2021-Dec-12 00:03:23.4890837
   ||    ||
   ||   //
   ||__//
   ’---
    \  \
    /  /
    ^  ^

今後の予定

実用的な段階としては、D言語で書いたコード解析ツールなどをプロセス起動・通信などのオーバーヘッド無しでサクッと呼んでEmacsでのD言語の編集を快適にしたい。

あとEmacs lispは好きだけど、スコープと型がわかりやすくコンパイルも実行速いD言語で全部書きたい。具体的には先日D言語のimport文を辞書順にソートするツールを作ったので組み込みたい。 https://github.com/ShigekiKarita/d-import-sort

補足

Emacs lispをD言語が置き換える日まで serve-d を使おう。簡単な設定ガイドを書きました。

https://github.com/Pure-D/serve-d/blob/master/editor-emacs.md