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 ウィンドウフォーカス切り替え

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

UbuntuにおけるVSCodeのCommon Lisp設定

VSCode上でCommon Lispを書くための設定について。

Common Lisp - Visual Studio Marketplace

確認したバージョン

roswellでCommon Lisp環境を整えるのが前提。

  • Visual Studio Code : 1.59
  • roswell : 21.06.14.110(c0bc597)
  • SBCL : 2.1.3
  • linedit : 4cfaf45da978db5d8d0171ecba7223edc62c0910
  • prepl : 9491436d50d5d7ce9c937d2083694f9aa79b615a
  • cl-lsp : 0465217cfd85a744dd46a0e66274feb68503dd1e

バックエンド設定

prepl が sb-kernel:layout-info を使っているが、sbcl 2.1.4でなくなった(?)ため2.1.3を使う。

ros install sbcl-bin/2.1.3

LSP バックエンドをインストールする。 ライブラリは修正の入っている ailisp さんのをインストール。

ros install ailisp/linedit
ros install ailisp/prepl
ros install ailisp/cl-lsp

VSCode 設定

  1. VSCode を開く
  2. Ctrl + P
  3. ext install ailisp.commonlisp-vscode

実行

Common Lispコードを開くとシンタックスハイライトがつく。

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

Ctrl + Shift + Enter でREPLが起動する。

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

lispファイル上の関数でCtrl + Enterを実行すると関数がコンパイルされ、REPL側で使えるようになる。 REPL側でも Tab でシンボルの補完候補が出る。

ACL2を触ってみる

最近形式検証に興味があるので、ACL2を触ってみた。

形式検証とは

仕様を特定の形式で書くと正しさをチェックしてくれる、らしい。

ALC2とは

Common Lispで書かれた形式検証のツール。

環境

uname -a
Linux gx502g 5.4.0-77-generic #86-Ubuntu SMP Thu Jun 17 02:35:03 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
ros --version
roswell 21.06.14.110(c0bc597)
ros run -- --version
SBCL 2.1.6

インストール

asdはなさそうでmakeする必要がある。

ACL2 Version 8.3 Installation Guide

ダウンロードして展開する。

wget https://github.com/acl2/acl2/archive/refs/tags/8.3.tar.gz
tar xf 8.3.tar.gz

ビルドして.localに実行ファイルをコピーする。

cd acl2-8.3/
make LISP="ros run"
cp saved_acl2 $HOME/.local/bin

手順にしたがってbooksをビルドする。

cd books
make -j 16 ACL2=$HOME/.local/bin/saved_acl2 basic

チュートリアル

チュートリアルA Walking Tour of ACL2 をやってみる。

saved_acl2

で起動して、説明読みつつ実行してみるが、これ自動定理証明系か。 これ自体はよいものかもしれんが、俺が今欲しかったのはこういうのじゃなかった。。。