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 LispCommon Lispに近く6割程度の仕様が実装されている。
Emacs Lispとの互換性はあまり無い。その一方で、Windows APIにアクセスできるなど、Windowsネイティブのソフトウェアである利点をいかした作りになっている。

http://ja.wikipedia.org/wiki/Xyzzy

リーダー機能については、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までコメントをお寄せ下さい。