difftasticをCommon Lispで使ってみる

Difftastic, a structural diffCommon Lispで使ってみた。

cargoでインストールする。

cargo install --locked difftastic
(略)
  Installing /home/gos-k/.cargo/bin/difft
   Installed package `difftastic v0.53.1` (executable `difft`)

以下の2ファイルを比較してみる。

(defun alfa (bravo)
  (charlie delta))
(defun alfa ()
  (delta (charlie)
         echo))

通常の diff で比較した場合。

difftasticの difft で比較した場合。

括弧を考慮しつつ変更部分を色分けしてくれる。 便利そう。

roswell2をインストールする

Lisp Advent Calendar 2023 - Adventar の6日目の記事です。

以前からCommon Lispの処理系マネージャ(?)である roswell は存在していたが、新に roswell2 が出てきたので使ってみる。 roswellとroswell2の大きな違いは、roswellが複数OSへの移植性を考慮して下位層がC上位層がCommon Lispで書かれていたのに対し、roswell2では全てをCommon Lispで書き直している。

動作確認したバージョン

インストール

ビルド用にaptでOS側のパッケージをインストールする。

sudo apt install -y git make docker.io
sudo gpasswd -a $USERNAME docker

docker関連でgroupを設定したので再ログインする。

githubからroswell2をもらってくる。

git clone git@github.com:roswell/roswell2.git

ubuntu上でのビルドなのでlinux-buildでmakeを行う。

cd roswell2
make linux-build

最終的にこんな感じに出力されたら成功。

...
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses.  See the CREDITS and COPYING files in the
distribution for more information.
[undoing binding stack and other enclosing state... done]
[performing final GC... done]
[defragmenting immobile space... (fin,inst,fdefn,code,sym)=883+953+18675+20067+26865... done]
[saving current Lisp image into bin/lisp:
writing 0 bytes from the read-only space at 0x50000000
writing 896 bytes from the static space at 0x50100000
writing 33308672 bytes from the dynamic space at 0x1000000000
writing 2064384 bytes from the immobile space at 0x50200000
writing 12984320 bytes from the immobile space at 0x52a00000
done]

ローカルディレクトリにインストールする。

PREFIX=$HOME/.local make install

最終的にこんな感じに出力されたら成功。

echo 9820c6a Mon Nov 6 18:03:20 2023 +0000 > lib/commit
mkdir -p /home/roswell2/.local/bin
cp -f bin/lisp /home/roswell2/.local/bin
mkdir -p /home/roswell2/.local/lib/roswell
cp -r lib/* /home/roswell2/.local/lib/roswell

元々 .local/bin がなくてパスが通ってないので再ログインする。

何かをリビルドする。 コマンド名が、roswellでは ros だったが、roswell2からは lisp に変更になっているので注意。

lisp rebuild

最終的にこんな感じに出力されたら成功。

...
To load "roswell2.cmd.version":
  Load 1 ASDF system:
    roswell2.cmd.version
; Loading "roswell2.cmd.version"

To load "roswell2/config.default":
  Load 1 ASDF system:
    roswell2/config.default
; Loading "roswell2/config.default"

とりあえず実行してみる。

lisp run -L sbcl

最後に * が出たらreplの起動成功なので普通にlispコードを実行してみる。

* (remove-duplicates '(:alfa :alfa :bravo :bravo))
(:ALFA :BRAVO)

roswell2のインストールが出来た。

Common Lispで関数呼出を辿る

通常プログラムを書いていると、関数を書いて、その関数を呼び出す関数を書いて、というような木構造とか階層構造とか複雑になっていく。 これを辿る方法について。

実行環境はlemのslime上で、ccl 1.12.1が前半でsbcl 2.2.9が後半。

slimeの挙動

slimeには M-. で関数定義にジャンプする機能があるが、これは swankfind-definitions-for-emacs により実現されており、repl上からも使える。

(swank:find-definitions-for-emacs "mapcar")
(("#'MAPCAR" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/lists.lisp") (:POSITION 26761) NIL))
 ("(COMPILER-MACRO MAPCAR)" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/compiler/optimizers.lisp") (:POSITION 33286) NIL))) 

mapcar という名前だけからは複数の該当個所が出てくる。

逆に M-_ で関数利用先は swank の xrefs

(swank:xrefs '(:calls :macroexpands :binds :references :sets :specializes) "mapcar") 
((:CALLS ("#'UIOP/UTILITY:WHILE-COLLECTING" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/tools/asdf.lisp") (:POSITION 57642) (:SNIPPET "(defmacro while-collecting ((&rest colle")))
         ("#'SWANK::MAP-IF" (:LOCATION (:FILE "/home/gos-k/.roswell/lisp/quicklisp/dists/quicklisp/software/slime-v2.27/swank.lisp") (:POSITION 105172) (:SNIPPET "(defun map-if (test fn &rest lists)
  \"L")) )
         ("#'SWANK::SCORE-COMPLETION" (:LOCATION (:FILE "/home/gos-k/.roswell/lisp/quicklisp/dists/quicklisp/software/slime-v2.27/contrib/swank-fuzzy.lisp") (:POSITION 29566) (:SNIPPET "(defun score-completion (completion shor")))
         ("#'CCL::FIXED-VALUES-OP" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/level-1/l1-typesys.lisp") (:POSITION 32586) NIL))
         ("(:METHOD CCL::FORCE-BREAK-IN-LISTENER (#<STANDARD-CLASS PROCESS>))" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/level-1/l1-events.lisp") (:POSITION 3880) NIL))
         ("#'CCL::DECOMP-LAMBDA-LIST" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/compiler/nx-basic.lisp") (:POSITION 43172) NIL))
         ("#'CCL::NX1-TYPESPEC-FOR-TYPEP" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/compiler/nx1.lisp") (:POSITION 2900) NIL))
         ("#'CCL::DEFAULT-SETF" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/macros.lisp") (:POSITION 26221) NIL))
         ("#'RESTART-BIND" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/macros.lisp") (:POSITION 8651) NIL))
         ("#'CCL::BITOPF" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/macros.lisp") (:POSITION 52042) NIL))
         ("#'CCL::LONG-FORM-DEFINE-METHOD-COMBINATION" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/method-combination.lisp") (:POSITION 24944) NIL))
         ("#'CCL::REFERENCE-METHOD" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/macros.lisp") (:POSITION 126784) NIL))
         ("#'POP" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/setf.lisp") (:POSITION 30800) NIL))
         ("#'PUSHNEW" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/setf.lisp") (:POSITION 30051) NIL))
         ("#'PUSH" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/setf.lisp") (:POSITION 29432) NIL))
         ("#'DECF" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/setf.lisp") (:POSITION 14220) NIL))
         ("#'INCF" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/setf.lisp") (:POSITION 13504) NIL))
         ("#'DEFSETF" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/setf.lisp") (:POSITION 8755) NIL))
         ("#'CCL::DEFSTRUCT-BOA-CONSTRUCTOR" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/defstruct-lds.lisp") (:POSITION 14720) NIL))
         ("#'CCL::%TEMP-PUSH" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/compiler/nxenv.lisp") (:POSITION 37263) NIL))
         ("#'CCL::TRACE-PRINT-BODY" (:LOCATION (:FILE "/home/gos-k/.roswell/impls/x86-64/linux/ccl-bin/1.12.1/lib/encapsulate.lisp") (:POSITION 11734) NIL))))

これらはテキストエディタを前提としてるので、カーソル位置から名前を取り、それによって検索している。
欲しいものに近いがちょっと違って、指定した関数の中で呼び出されている関数のリストが欲しい。

呼び出している関数のリスト

xrefs:callees を渡すと取れるみたいだが、cclだと動かなかったのでsbclで試す。

repl上で2つ関数定義する。

(defun alfa () (pprint :alfa))
(defun bravo () (alfa))

bravoを指定する。

(swank:xrefs '(:callees) "bravo")

呼び出してるalfaが取れる。 repl上で定義してるのでソースファイルがないエラーになってる。

((:CALLEES ("ALFA" (:ERROR "Error: DEFINITION-SOURCE of function ALFA did not contain meaningful information."))))

alfaを指定する。

(swank:xrefs '(:callees) "alfa")

呼び出しているpprintが取れる。

((:CALLEES
  ("PPRINT"
   (:LOCATION (:FILE "/home/gos-k/.roswell/src/sbcl-2.2.9/src/code/print.lisp") (:POSITION 6690)
    (:SNIPPET "(defun pprint (object &optional stream)
  \"Prettily output OBJECT preceded by a newline.\"
  (declare (explicit-check))
  (let ((*print-pretty* t)
        (*print-escape* t)
        (stream (out-stream-from-designator stream)))
    (terpri stream)
    (outp")))))

これを再帰的に行うと、

(defun trace-functions (name)
  (pprint name)
  (let* ((refs (swank:xrefs '(:callees) name))
         (callees (last (first refs))))
    (dolist (callee callees)
      (trace-functions (first callee)))))

(trace-functions "bravo")
"bravo"
"ALFA"
"PPRINT"
"TERPRI"

多分関数が辿れた。

ついでに xrefs 自体も辿ってみる。

(trace-functions "swank:xrefs")
"swank:xrefs"
"SWANK:XREF"
"SWANK:FROM-STRING"
"SWANK::CALL-WITH-BUFFER-SYNTAX"
"SWANK/BACKEND:CALL-WITH-SYNTAX-HOOKS"
"SB-IMPL::GET3"
"SYMBOL-PLIST"

valuesのvalues

Common Lispには多値が存在するが、多値を要素にもつ多値が扱えるのかどうか何となく気になったので実験。 実行環境はUbuntu 22.04 amd64とCCL 1.12.1。

多値を返す関数と多値の受け取り。

(defun f ()
  (values :alfa :bravo))

(multiple-value-bind (a b) (f)
  (pprint a)
  (pprint b))

(pprint (f))
:ALFA
:BRAVO
:ALFA

valuesをマクロ展開してみる。

(macroexpand '(values :alfa :bravo))

valuesはマクロじゃなかった。

(VALUES :ALFA :BRAVO)

multiple-value-bindをマクロ展開してみる。

(macroexpand '(multiple-value-bind (a b) (f)
  (pprint a)
  (pprint b)))

中身はmultiple-value-callだった。

(MULTIPLE-VALUE-CALL #'(LAMBDA (&OPTIONAL A B &REST #:IGNORE) (DECLARE (IGNORE #:IGNORE)) (PPRINT A) (PPRINT B)) (F))

multiple-value-callにvaluesを持つvaluesを渡してみる。

(multiple-value-call #'list (values (values :alfa :bravo) :charlie))

:bravoが取れない。

(:ALFA :CHARLIE)

Claspをビルドする2021-11-19

そろそろClaspは1.0が出そうだが、着実にバージョンアップが依存ライブラリの方も上がっており、 Claspをビルドする2020-05 - gos-k’s blog 従来の手順をなぞってもビルドできない。

なので再びビルドに挑戦する。

ビルド手順としての主な変更は恐らくLLVM 13に依存ライブラリーが変わったこと。

dockerを立ち上げる。

docker pull ubuntu:impish
docker run -it --name roswell-clasp ubuntu:impish

関係ライブラリーをインストールする。

apt update
apt install -y sbcl
apt install -y git make build-essential curl
apt install -y libboost-filesystem-dev libboost-graph-dev libboost-regex-dev libboost-date-time-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev
apt install -y libgmp-dev libffi-dev zlib1g-dev libelf-dev libncurses-dev libbsd-dev
apt install -y llvm-13 clang-13 libclang-13-dev
apt install -y python
apt install -y wget libgc-dev
apt install -y language-pack-en

clasp用のboehm gcをビルドする。

git clone https://github.com/clasp-developers/clasp-boehm
cd clasp-boehm
mkdir -p /opt/clasp-support
make get
make build
make -C gc-8.0.4 install

claspをビルドする。

git clone https://github.com/clasp-developers/clasp.git
cd clasp
git checkout 01e77db41ae1927dbe33489a27f78ad38ad5288f
cp wscript.config.debian10 wscript.config
echo 'INCLUDES = ["/opt/clasp-support/include"]' >> wscript.config
echo 'LINKFLAGS = ["-L/opt/clasp-support/lib"]' >> wscript.config
./waf distclean
./waf configure
./waf build_dboehmprecise

実行する。

/build/clasp 
../../src/llvmo/llvmoExpose.cc:4978:operator() CLASP_NO_JIT_GDB not defined Adding ObjLinkingLayer plugin for orc::createJITLoaderGDBRegistrar
Starting cclasp-boehmprecise-0.4.2-4831-g01e77db41-cst ... loading image...
Top level in: #<PROCESS TOP-LEVEL @0x7f5dbbe21f09 (Running)>.
COMMON-LISP-USER> 

未だにバージョン番号らしきのが0.4.2だが、果たしてバージョンアップされるんだろうか

UbuntuにおけるVSCodeのCommon Lisp設定その2

今度はVSCodeの前回とは別のCommon Lisp拡張である Alive - Visual Studio Marketplace を使ってみる。

前回のは現状インストール数6kくらいなのに対してこれはインストール数600くらいで少ないが、Using VSCode with Alive を見るかぎり機能的にはこちらの方が豊富そうに見える。

動作確認したバージョン

  • Ubuntu : 20.04
  • roswell : 21.06.14.110(c0bc597)
  • sbcl : 2.1.9
  • VSCode : 1.61.2
  • Alive : 0.2.4

インストール

  1. VSCode を開く
  2. Ctrl + P
  3. ext install rheller.alive

シンタックスハイライト

発展途上の機能らしく、キーワードシンボル等をハイライトしない。

f:id:gos-k:20211024134656p:plain

Roswellの設定

settings.jsonにREPLでRoswellを起動する設定をする。

    "alive.swank.startupCommand": [
        "ros",
        "run",
        "--system",
        "swank",
        "--eval",
        "(swank:create-server)"
    ],

REPL起動

  1. lispファイルを開く
  2. Ctrl + Shift + P
  3. Alive: Start REPL And Attach を選ぶ

これで画面が分割されREPLが起動する。 他のAlive関連コマンドで結構ショートカットキーがあるのにこれはなんでないんだろうか?

f:id:gos-k:20211024140259p:plain

何も式を入力せずにEnter押すと、CL-USER>が評価されてデバッガに突入する。

f:id:gos-k:20211024141639p:plain

他のテキストエディタだとRestertsの選択肢を数字のキー入力で選択できるが、Aliveの場合はクリックしないとダメらしい。

式の評価自体は普通にキー入力で行われる。

CL-USER> (+ 1 1)

Enter で評価される。

(+ 1 1)
2

CL-USER>

Qlotを使う場合

Qlotを使う場合はsettings.jsonを書き換える。

    "alive.swank.startupCommand": [
        "qlot",
        "exec",
        "ros",
        "run",
        "--system",
        "swank",
        "--eval",
        "(swank:create-server)"
    ]

単独のrosと使い分けたいから、設定変えるだけじゃなくて本体に何か手を入れ他方がいいか。

その他

F12 でカーソル位置のシンボル定義に飛べたり色々便利機能の実装もある。

一つ困るのが、REPLを使っていて数千行とか増えていくと徐々に動作が遅くなっていく。 これはたまにREPL上の全てを選択して削除すると元の早さに戻る。

GNOMEのショートカットキー

結果的に現状GNOME使ってるのでショートカットキーを覚える。

便利なキーボードショートカット

タイル型ウィンドマネージャを使ってて結局自分が欲しいウィンドウの操作って、 デスクトップの移動と左右半分の拡大とフォーカスの移動なので実際はこの位?

  • Ctrl + Alt + → 右デスクトップに切り替え
  • Ctrl + Alt + ← 左デスクトップに切り替え
  • Super + → ウィンドウ右半分
  • Super + ← ウィンドウ左半分
  • Super + ↑ ウィンドウ最大化
  • Super + Tab ウィンドウフォーカス切り替え

後はいくつかよく使うアプリを起動できればいいが。。。