新千葉 ガーベージ・コレクション

FPGA マガジンやインターフェースで書けなかったこと等をちょぼちょぼ書いてます。@ryos36

common lisp で venv 的なことを

ASDL で出来るのだろうなと思い調べてみるとありますね。

diary.wshito.com

この通りにやるとできます。ASDF の Ver3 に移行したので、すでに .config/common-lisp/source-registry.conf.d をつかってます(てのはすでにブログに書いた) asdf を久々に使う - 新千葉 ガーベージ・コレクション

さて、あとは動的に出来るはず。禁じ手的な asdf:central-registry を書き換える(これは Ver2. の手法なのでおすすめされていない) がありますが(通常の registry より後方互換のために先に読まれる模様)

asdf:initialize-source-registry で設定すれば動的に出来ます。はい。その際に DSL (S 式だけどこの ASDF アプリローカルな記述ね) を使えば、あとからロードパスを設定出来てそれは優先順位的に先に設定されるもよう。引数には :inherit-configuration か :ignore-inherited-configuration を設定する。前者は既にある設定を引き継ぐ(つまりいま設定しようとしている値を足す)、後者は既にある設定を無視して新たに設定しなおす。

DSL の詳細はこちら ASDF マニュアル

あと asdf.conf というファイルを置いておくと自動的に読み込まれるみたいね(試してない。既出の wshito さんのページ 参照の事)

ということで、ローカルにプロジェクトをダウンロードして自分でディレクトリ設定してそこにバージョン違いのものを入れておけば、複数の違うバージョンのものがインストール可能。

ついでに書くと asdf:user-cache を上書きすると、普段は ~/.cache/.... の下にコンパイルされたオブジェクトが置かれるけど、それを変えることが出来る。

(setf asdf:*user-cache* #P"/home/user/Works/my-cache/")

こんなかんじ。 あ~これ便利だわ。

perl の locale failed

なんかしらんが ubuntu ( Linux subsystem だったりするが ubuntu 全般だろう) で perl を使うと failed とか言われる。

> perl -v
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
        LANGUAGE = (unset),
        LC_ALL = (unset),
        LANG = "ja_JP.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").

.cshrc には (bash じゃない!!) には LANG の設定しているがどうやらシステムでちゃんと設定しないとだめらしい。最初 /etc/locale.gen を”直接"編集しようと思ったがいくらなんでもこれじゃだめだろう。さがせば答えがあるね。

qiita.com

> sudo apt-get install language-pack-ja
> sudo update-locale LANG=ja_JP.UTF-8

これでどうよ?

> perl -v
This is perl 5, version 26, subversion 1 (v5.26.1) built for x86_64-linux-gnu-thread-multi
(with 67 registered patches, see perl -V for more detail)

Copyright 1987-2017, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at http://www.perl.org/, the Perl Home Page.

おー大丈夫になった。

とりあえず hunchentoot ふたたび

ubuntu で試す

sudo apt install cl-hunchentoot でいけます。 インストールしただけだと何も使えません。悪いことに ubuntu のパッケージからは test が削られてます。まずは”素”の hunchentoot を動かします。

acceptor で動かす(ほとんど素)

#-:asdf (load "/usr/share/common-lisp/source/cl-asdf/asdf.lisp")
(require :hunchentoot)
(require :cl-who)
(setq hunchentoot:*hunchentoot-default-external-format*
     (flex:make-external-format :utf-8 :eol-style :lf))
(setq hunchentoot:*default-content-type* "text/html; charset=utf-8")

(hunchentoot:start
      (make-instance 'hunchentoot:acceptor :port 4242))

これで動きます。 localhost:4242 にアクセスして welcome のページが見れればまずは OK です。 f:id:ryos36:20190228152021p:plain

さて、これどこを見ているかというと、default の root というのが”コンパイル時"に決まるようになっていて、ubuntu で標準にいれれは /usr/share/common-lisp/source/hunchentoot/www です。 参考までに書くと make-instance 時に :document-root を指定すればそこが標準の root になります。

  (make-instance 'hunchentoot:acceptor :port 4242 :document-root "/home/WWW")

easy-acceptor で動かす

cgi 的なことが簡単にできます。easy と書いてありますが、あまり細かい設定をする必要がない場合、というかたいていの場合ではこちらを使うことになるかと思います。素の acceptor は本当に素で(機能的に分離したようだ)、dispatch-table 的なものすらありません(たぶん)。なので、acceptor を単純に使おうとすると、”恐らく"自分で handler を書き直さないといけなくて、その場合は acceptor を継承した class を作る必要がありそうです。 以前の hunchentoot は dispatch-table を自分で頑張って書いていた印象がありますが(うろおぼえ)、easy-acceptor を使えば handler を定義するだけで OK です。

#-:asdf (load "/usr/share/common-lisp/source/cl-asdf/asdf.lisp")
(require :hunchentoot)
(require :cl-who)
(setq hunchentoot:*hunchentoot-default-external-format*
     (flex:make-external-format :utf-8 :eol-style :lf))
(setq hunchentoot:*default-content-type* "text/html; charset=utf-8")

(hunchentoot:start
      (make-instance 'hunchentoot:easy-acceptor :port 4242))

(hunchentoot:define-easy-handler (say-yo :uri "/yo") (name)
  (setf (hunchentoot:content-type*) "text/plain")
  (format nil "Hey~@[ ~A~]!" name))

はい clisp でも sbcl でも ubuntu 上で動きました(追記: clisp 不安定。sbcl がおすすめです)。localhost:4242/yo にアクセスできれば OK です。localhost:4242/yo?name=yoyo などとラッパーの掛け合いよろしく遊んでください。 上のソースは実験的に start を先にしていますが、通常は逆でしょうね(start が後)。

古い情報に注意しましょう

この blog は 2019/2/28 に書いてますが、私はその情報で右往左往しました。 たぶん hunchentoot のバージョンが上がったことによる差異でしょう。今後は hunchentoot を使う時はパッケージ作って、バージョンチェックしないとダメかも。

まずは hunchentoot:start-server を使っているもの。相当古いので使えません。(私が知っていたのはこのあたり) 2008 年頃の blog に見ることが出来ます。 2011 年頃は、情報が途中ですね。acceptor だとたぶんほとんどと何もできません。サーバが立ち上がるので間違ってはないのですが。いくつかのサイトは acceptor をつかっています。easy-acceptor じゃないと動きませんでした。たぶん、いまや easy-acceptor でないと動かないと思います。

いろいろ調べる羽目に

動かすためにいろいろ調べる羽目になってしまいました。まずは asdf の設定を変えました。keen さん?とかを参考に ~/.config/common-lisp/source-registry.conf.d/ をつくり、10-systems.conf に (:tree "/usr/share/common-lisp/source/") と書いてます。そこで 00-debug.conf とかつくって (:tree (:home "Lisp/")) として、そのディレクトリに git から hunchentoot と usocket を持ってきました。usocket を持ってきた理由は、新しいバージョンの hunchentoot が新しいバージョンの usocket に依存していたためです。(めんどくさ~~い) これで最初は log-message* 関数を使ってログを出し(エラー出力に表示される)、最後はソース読んで改変して動きを確かめました。一苦労でした。

じみな macro

macrolet をつかってみる。だんだん世の中から外れていく気がする。

(defmacro make-style-lambda (style-desc)
  (macrolet ((make-style-lambda0 (style-desc)
              `#'(lambda ,
                   (remove-duplicates
                     (remove nil
                             (mapcar
                               #'(lambda (i)
                                   (if (listp i)
                                     (let ((qkey (car i))
                                           (arg-sym (cadr i)))
                                       (if (or (eq qkey 'SYSTEM::UNQUOTE)
                                               (eq qkey 'SYSTEM::SPLICE))
                                         arg-sym))))
                               (cadr style-desc))) :from-end t) ,style-desc)))
    (if (atom style-desc) (eval `(make-style-lambda0 ,(eval style-desc)))
      `(make-style-lambda0 ,style-desc))))

これで (make-style-lambda `(:a ,arg) とやっても

(let ((x `(:a ,arg))
   (make-style-lambda x))

としても思い通りの lambda が出来るようになった。これ最後 funcall するんだよね。funcall するマクロつくればいいね。

(defmacro do-w (x &body body) `(funcall (make-style-lambda ,x) ,@body))
DO-W
> (do-w `(:a ,arg) "arg")

おーできた。

> (do-w `(:a ,arg) "arg")
(:A "arg")
> (do-w x "abc")
(:A "abc")

うまく動いている気がする。

slimv と slime-vim 使ってみたものの

swank サーバを立てるのは簡単

これはたとえば ubuntu なら apt install cl-swank とかして /usr/share/common-lisp/source/slime/start-swank.lisp を動かせばよい。clisp でも sbcl でもどちらでもよい。というか通常は自動起動するから(vimemacs から) そもそも起動しなくてもよい。

slime-vim

まず vim 用の slime が必要だ。slimv と slime-vim というのがあるらしい。後者はどうやら tmux などのコピー&ペースト機能を利用して repl に対して文字列を送る。swank サーバに対応しているかどうはわからない。対応してないと判断してつかうのはやめた。シンプルなんだけど、tmux で切り替える手間や、コピペに失敗する?みたいで tmux がときどき乱れるので安定性に欠く。

slimv

ということで slimv 。どうも、インストール方法で動いたり動かなかったりするみたいだ。私は単純に .vim/plugin に github から clone した。README.txt にはこれでよいと書いてある。ところが動かない。これ設定になんかコツが必要みたいだ。SlimvInitBuffer がないといって怒られる。

ところが SlimvInitBuffer はちゃんと ftplugin/slimv.vim で定義されている。ということでこれはおかしい。そこで、ftplugin//.vim の最後で call SlimvInitBuffer() しているところをコメントアウト。.vimrc のコメントアウトは "" でくくるのね。

なんか様子がおかしいので lisp 以外の clojure r schemeディレクトリをざっくり削除。気分はブラックジャック。そして

let g:slimv_swank_cmd = '! tmux new-window -d -n REPL-CLISP "clisp /usr/share/common-lisp/source/slime/start-swank.lisp "'

を追加。slimv の README.txt にいくつかサンプルが載っている。tmux を起動して vi ~/tmp/test.lisp を編集。 call SlimvInitBuffer() を実行。そして , + b とするとサーバが立ち上がった。TCP の名前つきの socket (だっけ?) でコネクトしている模様。

, + e などで一行だけの評価可能。エラーを起こすと lisp が死ぬ。まぁここまではいいとして、、、

slimv をつかうと、作者の好みの設定に強引させられてしまう。まず ()の自動挿入。非常に鬱陶しい。これは let g:paredit_mode = 0 で回避できた。さらに Hyperspec (だっけ?) が勝手に Help してくれる。非常に鬱陶しい。考えの邪魔になる。これはどうやっても抑制できなかった。

画面が2つに分かれるのも気に食わない。

f:id:ryos36:20190227164828p:plain
slimv による起動

ということで、使うのをやめました。当面 tmux で十分という事で。swank サーバはいいアイデアなんだけどな。

複雑な(?)マクロ

  • まずは目標
"abc" と `(:tag ,aa)  -> (:tag "abc")

としたい。 簡単には

(defun f (arg) `(:tag ,arg))

これでいいのか、、、。でも defun とかが冗長。

(defmacro m0 (name arg tlst) `(defun ,name (,arg) ,tlst))

 (m0 ff arg `(:tag ,arg))

 (ff "abc")
(:TAG "abc")

なんとなく関数を作るマクロが出来た。lambda にしてみる。

(defmacro m00 (arg tlst) `#'(lambda (,arg) ,tlst))

 (funcall (m00 arg `(:tag ,arg)) "abc")
(:TAG "abc")

できた。arg が冗長。

(defmacro m00 (tlst) `#'(lambda ,(remove nil (mapcar #'(lambda (i) (if (and (listp i) (eq (car i) 'SYSTEM::UNQUOTE)) (cadr i))) (cadr tlst))) ,tlst))

(m00 `(:tag ,arg))
#<FUNCTION :LAMBDA (ARG) `(:TAG ,ARG)>

(funcall (m00 `(:tag ,arg)) "abc")
(:TAG "abc")

より複雑なのが出来た。SYSTEM::UNIQUOTE は他の lisp で他の大丈夫か不安。 データを作る。

(defparameter *my-list*
   '((:tag . (m00 `(:tag ,arg)))
     (:tag2 . (m00 `(:tag2 :local-tag "local" ,arg)))))

(setf input-data '(("abc" . :tag) ("def" . :tag2)))

(funcall (eval (cdr (assoc :tag *my-list*))) "abc")
(:TAG "abc")

input-data を評価する関数を作る。

(defun eval-input-data (lst) (mapcar #'(lambda (x) (let ((word (car x)) (
tag (cdr x))) (funcall (eval (cdr (assoc tag *my-list*))) word))) lst))

(eval-input-data input-data )
((:TAG "abc") (:TAG2 :LOCAL-TAG "local" "def"))

なんとなくできた。毎回 eval するのは馬鹿らしいので cache する。

(defun eval-input-data (lst) (mapcar #'(lambda (x) (let ((word (car x)) (
tag (cdr x))) (let* ((func (cdr (assoc tag *cached-my-list*))) (new-func (if fun
c func (eval (cdr (assoc tag *my-list*)))))) (if (null func) (push `(,tag . ,new
-func) *cached-my-list*)) (funcall new-func word)))) lst))

(defparameter *cached-my-list* nil)

*cached-my-list*
NIL

(eval-input-data input-data )
((:TAG "abc") (:TAG2 :LOCAL-TAG "local" "def"))

 *cached-my-list*
((:TAG2 . #<FUNCTION :LAMBDA (ARG) `(:TAG2 :LOCAL-TAG "local" ,ARG)>)
 (:TAG . #<FUNCTION :LAMBDA (ARG) `(:TAG ,ARG)>))

(eval-input-data '(("fff" . :tag)))
((:TAG "fff"))

なんかできている気がする。 my-list の m00 が余計だ。なんとかならないのか?これはまた考える。