@peccul is peccu

(love peccu '(emacs lisp cat outdoor bicycle mac linux coffee))

フロントエンドもサーバーサイドもCommon Lispで書く試み

'(qlot Caveman2 Clack Roswell jscl)この辺りの組み合わせでようやく雛形のようなものができた。

やろうとしたこと

jsclというCommon LispJavaScriptコンパイルしてくれるものを使って、フロントエンドもCommon Lispで書こうとした。

github.com

見よう見まねで作ったので、そこはこうするのが良い。といったことがあればコメントなりTwitterでなり指摘していただけると喜びます。

Lisp Advent Calendar 2016の記事です。

Node.jsに慣れすぎて、Common Lispだと何かにつけてはまる。はまるたびに正解がわからない。

jsclを使ってコンパイルするところで力尽きたので、jsclでフロントエンドというところまでできていない。

成果物

github.com

やっていることは/lisp/:lisp.jsというパスへのアクセスに対し、:lisp.jsがあればそれを返し、なければ同名の:lisp.lispファイルをコンパイルして返すだけ。

起動させるには

Roswellをインストール済みとして、下記一連のコマンドで起動させられる。 Intel Edisonで動作確認したところsbcl/1.3.11 だとironcladがエラー吐いたので1.3.9を使った。

$ ros install sbcl/1.3.9
$ ros install peccu/caveman-jscl
$ ros install qlot
$ cd ~/.roswell/local-projects/peccu/caveman-jscl
$ qlot install
$ ros install clack
$ qlot exec clackup app.lisp
...
Hunchentoot server is going to start.
Listening on localhost:5000.

これで http://localhost:5000/ にアクセスすると/lisp/log.js を読み込んでコンソール出力しているのが見える.

f:id:peccu:20161128164657p:plain

static/lisp/*.lispをおけば/lisp/*.jsでアクセスできるようになる。 一応ファイルの更新日時を見てlispの方が新しければ再コンパイルするようにしている。

サンプルではlog.lisp という下記のようなファイルを設置している.

(#j:console:log "")
(#j:console:log "--------lisp was compiled!!--------")

コンパイルされたjs

(function(jscl) {
    'use strict';
    (function(values, internals) {
        var l1 = internals.make_lisp_string('');
        var l2 = internals.intern('*ROOT*');
        var l3 = internals.make_lisp_string('console');
        var l4 = internals.make_lisp_string('log');
        internals.js_to_lisp(internals.symbolValue(l2)[internals.xstring(l3)][internals.xstring(l4)](internals.lisp_to_js(l1)));
        var l5 = internals.make_lisp_string('--------lisp was compiled!!--------');
        var l6 = internals.make_lisp_string('console');
        var l7 = internals.make_lisp_string('log');
        internals.js_to_lisp(internals.symbolValue(l2)[internals.xstring(l6)][internals.xstring(l7)](internals.lisp_to_js(l5)));
    })(jscl.internals.pv, jscl.internals);
})(typeof require !== 'undefined' ? require('./jscl') : window.jscl)

lispコンパイルする部分

  • src/web.lisp

caveman-jscl/web.lisp at 1bcf6684094f95520ef02f49ed2655411bdd7414 · peccu/caveman-jscl · GitHub

(defroute "/lisp/:lisp.js" (&key lisp)
  ;; jsファイルとlispファイルのパスを作る
  (let ((js (format nil "~alisp/~a.js" *static-directory* lisp))
        (lisp (format nil "~alisp/~a.lisp" *static-directory* lisp)))
    ;; jsファイルが存在しないか、jsファイルの方が古ければコンパイルする
    (when (or (and
               (not (probe-file js))
               (probe-file lisp))
              (< (file-write-date js) (file-write-date lisp)))
      (in-package :jscl)
      (jscl::compile-application `(,lisp) js)
      (in-package :caveman-jscl.web))
    ;; コンパイル結果、jsファイルが作成されていれば返し,そうでなければ404を返す
    (if (probe-file js)
        (render js)
        (throw-code 404))))

わからないところ

主にjsclを読み込むところで困った。 jsclがQuicklispに登録されていればよかったのだが未登録なので、Forkしたリポジトリでasdファイルを作ってqlotでインストールする形になっている。

  • defsystem:depends-ondefpackage:useの違いが未だにわからない。
    • depends-onに書くとQuicklispが一緒にインストールとロードしてくれる?
    • useに書くとロード済みなら、名前空間が利用できるようになる?
  • (ql:quick load :jscl)ではjscl.lispをロードしてくれるわけではなかったので,明示的に(load (merge-pathnames "jscl.lisp" (asdf:system-source-directory :jscl)))しているところをもっと正しくしたい
  • jscl側のasdファイルの書き方がわからない.(ql:quickload :jscl)した時にloadしたり何か実行する手段があるのか不明
  • qlot installではなくros install peccu/jsclでも同様に(ql:quickload :jscl)できる?

今後の展望

jscl側をいじり始めるとコンポーネント化したくなってくるので、jsclに乗っかるフレームワークを作りたくなってくる。

jsclによるフロントエンドはまた別の記事にする。