xyzzyのole-reader
この記事はLisp Reader Macro Advent Calendar 2012の記事です。
lisp方言のリーダーマクロの紹介という事で、Windows用テキストエディタ xyzzy のマクロ言語 xyzzy lisp よりole-readerをご紹介。
xyzzy lispのリーダーマクロ
まずはxyzzyをあまり知らない人向けにxyzzy lispのリーダー機能について簡単にご説明。
xyzzyはいわゆる1つのEmacsenってやつですが、マクロ言語はCommon LispのサブセットになっているためEmacs Lispとは結構違う所があります。
マクロ言語として採用されているxyzzy LispはCommon Lispに近く6割程度の仕様が実装されている。
http://ja.wikipedia.org/wiki/Xyzzy
Emacs Lispとの互換性はあまり無い。その一方で、Windows APIにアクセスできるなど、Windowsネイティブのソフトウェアである利点をいかした作りになっている。
リーダー機能については、CommonLispの全ての標準マクロ文字と #* (bit-vector), #P (pathname) 以外の全ての#ディスパッチマクロに対応しています。
;;; xyzzy lisp REPL user> #b01010101 85 user> #xffffffff 4294967295 user> #36r123456789abcdefghijklmnopqrstuvwxyz 86846823611197163108337531226495015298096208677436155 user> (exp (* pi #C(0 1))) #C(-1.0d0 1.224646799147353d-16) user> (aref #(1 2 3) 1) 2 user> (apply #'+ 1 2 3 '(4 5 6)) 21 user> (list #+xyzzy 1 #-xyzzy 2 #+cl 3 #|comment|# ) (1) user> #1=(format t "~S" '#1#) #1=(format t "~S" '#1#) nil
これらに加え、標準添付されているole拡張をロードするとole-reader #{ }
が使えるようになります。
xyzzyのole-reader
xyzzy lispにはOLEオートメーションサーバーを操作する為の関数がいくつか用意されています。
- ole-create-object
- ole-get-object
- ole-getprop
- ole-putprop
- ole-method
- etc..
これらを使ってコードを書くと、
;;; http://xyzzy.s53.xrea.com/reference/wiki.cgi?p=OLE%A5%AA%A1%BC%A5%C8%A5%E1%A1%BC%A5%B7%A5%E7%A5%F3%A4%CE%BB%C8%CD%D1%CE%E3 ; 意味もなく全部のシートに「東西南北」を書き込む (let* ((xl (ole-create-object "Excel.Application")) (book (ole-method (ole-getprop xl 'Workbooks) 'Add)) (numsh (ole-getprop (ole-getprop book 'Worksheets) 'count))) (dotimes (i numsh) (let ((sheet (ole-getprop book 'Worksheets (1+ i)))) (ole-putprop (ole-method sheet 'Range "A1:D1") 'Value #("東" "西" "南" "北")))) (ole-putprop xl 'Visible t))
ole-getprop, ole-methodがネストしまくってとても書きづらいし読みづらい。
そこでole-readerを使うと
(require 'ole) (let* ((xl (ole-create-object "Excel.Application")) (book #{xl.Workbooks.Add})) (dotimes (i #{book.Worksheets.Count}) (setf #{book.Worksheets[(1+ i)].Range["A1:D1"].Value} #("東" "西" "南" "北"))) (setf #{xl.Visible} t))
VBに近い形で書けるようになり、とてもスッキリします。
動作は単純で、
user> '#{book.Worksheets[(1+ i)].Range["A1:D1"].Value} (ole-method (ole-method (ole-method book ':Worksheets (1+ i)) ':Range "A1:D1") ':Value)
こんな風に展開しています。
実装を見ると分かるんですが、ドットがマクロ文字になっていて1つのトークンになっているので、ドットの前後にスペースや改行が入れられます。
user> '#{book.foo .bar .baz} (ole-method (ole-method (ole-method book ':foo) ':bar) ':baz)
この辺はVBよりもちょっと柔軟ですね。
このリーダーが使われているコードはあまり見かけないのですが、突如大量のExcelに関する作業が発生した時等にとても便利ですので、なんか変な方眼紙とか投げ付けられてVBAで片そうとしてブチ切れる前にカカッとxyzzyを起動してサクっとやっつけてみましょう。
oleメソッドの名前付き引数呼び出し
以下半分余談。
xyzzyのole-method関数には名前付き引数呼び出し機能がなく、
'VBA Worksheets(1).Copy After:=Workbook(2).Worksheets(1)
このような呼び出し方が出来ません。
Rubyのwin32oleだとHashを使って
# Ruby xl = WIN32OLE.new("Excel.Application") book = xl.Workbooks.Add book.Worksheets(1).Copy(:After => book.Worksheets(1))
こんな風に書けます。
しかしつい先日、この名前付き引数呼び出しに対応した関数が追加されました。
OLE メソッドの名前付き引数に対応 (#361) · c628b3d · xyzzy-022/xyzzy · GitHub
ole-readerの方の対応はまだなのですが、中の人からは以下の様な書き方が提案されてます。
#{excel.Copy[worksheet1 {:Count 3}]} ;; できるかな?
https://github.com/xyzzy-022/xyzzy/issues/361#issuecomment-10626273
他に良い記法のアイディアがありましたらこのissue #361までコメントをお寄せ下さい。