2009年9月21日月曜日

emacsからスクリプトエディターを開く

emacsで開いているapplescriptソースコードを、 ScriptEditor上で開くemacs lisp コマンド。

applescript-browse

(defun applescript-browse-url (script &optional action)
  "Convert script to applescript URL\n\
   action --- \"new\"(default) or \"insert\" or \"append\"\n\
   ex. (applescript-browse-url \"say time string of(current date)\")"
  (format "applescript://com.apple.scripteditor?action=%s&script=%s"
          (if action action "new") (url-hexify-string script)))

(defun applescript-browse (pfx)
  "バッファーの内容をApplescript URL protocolを使って、スクリプトエディター上に開く。\n\
  生成されたURLはキルリングに追加される。\n\
  リージョンを対象とする場合は、C-u で実行する。"
  (interactive "P")
  (save-excursion
    (let ((url
           (applescript-browse-url 
            (encode-coding-string
             (my-trim-string
              (apply (function buffer-substring-no-properties)
                     (if (equal pfx '(4))
                         (list (region-beginning) (region-end))
                       (list (point-min) (point-max)))))
             'utf-8))))
      (kill-new url)
      (browse-url url)
    )))

(defun my-trim-string (str)
  "stringの両端の空白を取り除く"
  (replace-regexp-in-string
   "^[ \\\t\\\r\\\n]+" ""
   (replace-regexp-in-string "[ \\\t\\\r\\\n]+$" "" str)))

使い方

M-x applescript-browseで、カレントバッファーの全内容が、 スクリプトエディターで開かれる。
リージョンを対象とする場合は、 C-u M-x applescript-browse で実行する。
また、applescript:// で始まるURLをキルリングに追加しているので、 applescript-browseを実行後、C-yでそれを貼付ける事ができる。

仕組みは、Applescript URL protocol supportというのを使って、 単にbrowse-urlしているだけだ。
URL protocol supportについては、 Script NoteさんAppleScript/URL PROTOCOL SUPPORT に、とてもわかりやすい説明がある。

2009年9月3日木曜日

schemeメモ - quoteで作ったリスト

単純に次ぎのコード、
(define (foo) '(a b c))
(foo) 
  ==> (a b c)
fooはどってことのない関数だが、実は、この書き方はとても危ない。

プログラムのどこかで、fooの出力を破壊的に変更してしまうと、
(reverse! (foo)) 
  ==> (c b a)
(foo) 
  ==> (a)
もはや、関数fooは (a b c)を返さなくなる。

vectorも同じだ。
(define (foo) '#(a b c))
(vector-set! (foo) 0 'aa)
(foo)
  ==> #(aa b c)

準クォートを使ったこれもだめだ
(define (foo x) `(,x a b c))
(foo 1)
  ==> (1 a b c)
(reverse! (foo 2))
  ==> (c b a 2)
(foo 1)
  ==> (1 a 2) 

しかしこれは大丈夫 (少なくともgoshでは)
(define (foo x) `(a b c ,x))
(foo 1)
  ==> (a b c 1)
(reverse! (foo 2))
  ==> (2 c b a)
(foo 1)
  ==> (a b c 1) 

無難な書き方はこうかな?
(define (foo x) (append `(,x a b c) '()))
(foo 1)
  ==> (1 a b c )
(reverse! (foo 2))
  ==> (c b a 2)
(foo 1)
  ==> (1 a b c )

まとめ
  • クォートで作ったリストやベクターは、静的にアロケートされている可能性がある
  • クォートで作ったリストやベクターに対し、直接に破壊操作を行わない。
  • 知らない関数の出力を破壊する時は、 前もってコピーしたものを破壊するのが無難。

しかしこれに慣れると、他の言語で同様なことを書くのに躊躇してしまいそうだ。
例えば、rubyだと
def foo ; return [1,2,3] ; end
foo.reverse!
  => [3, 2, 1]
foo
  => [1, 2, 3]
で全然問題ないのに、
def foo ; return [1,2,3].clone ; end 
と書いてしまうとか......

schemeメモ - appendの最後の引数

append の最後の引数はコピーされない!

R5RS の append の説明には、
The resulting list is always newly allocated, except that it shares structure with the last list argument.
と書いてある。荒っぽく訳すと、
appendは、いつも新規にアロケートしたリストを結果として返す。 ただし例外があり、最後の引数は構造を共有する。
ということは、(append list ... listN) の listN はコピーされないということか。 今までこんなことも知らずにコードを書いていたとは..... (^^;

実験してみた

> (define lst '(a b c))
> (eq? lst (cdr (append '(a) lst)))
  ==> #t  
確かに、最後の引数(a b c) は共有され、新規のアロケートはされていない。

もちろんこれもアロケートされない
> (eq? lst (append lst))
  ==> #t

とにかく最後の引数に () を置けば、みんなアロケートされる
> (eq? lst (cdr (append '(a) lst ())))
  ==> #f

単なるリストのコピーをしたければ
> (eq? lst (append lst ()))
  ==> #f
> (equal? lst (append lst ()))
  ==> #t

2009年9月1日火曜日

include マクロ

includeというマクロを書いてみた。 別ファイルに書かれてあるS式を、ソースの中に埋め込むマクロだ。 loadと似ているが、呼び出した場所の環境で評価される点が違う。

include.scm

;; ファイルに書かれているS式を埋め込むマクロ。
;; 使い方:  (include ファイル名)
;; ただし、ファイル名には文字列かトップレベルの変数しか指定できない
(defmacro include (fname)
  (let ((read-all
         (lambda (port)
           (let lp ((e (read port)) (result (list)))
             (if (eof-object? e) (reverse! result)
                 (lp (read port) (cons e result)))))))
    `(begin ,@(call-with-input-file 
                (eval fname (interaction-environment))
                (lambda (p) (read-all p))))))

適当なサンプルで動作確認

$ cat tmp2.scm    # 埋め込むファイル
  (display 
     (format "a=~A b=~A (foo a b) = ~A\n" a b (foo a b)))
  (foo a b)

$ gosh
  gosh> (load "./include.scm")
  gosh> (define file "tmp2.scm")
  gosh> (define foo +) (define a 10) (define b 20) ;; toplevel設定
  gosh> (let ((foo *) (a 11)) (load file))    ;; loadの実行結果
     =>  a=10 b=20 (foo a b) = 30
         #t
  gosh> (let ((foo *) (a 11)) (include file)) ;; includeの実行結果
     => a=11 b=20 (foo a b) = 220
        220
  gosh> ^D

変数 foo,a,bについて、 loadの場合はトップレベルの定義で評価されているが、 includeでは、(let ...) バインドの値で評価されているのが確認できる。

gosh,chezでの defmacro

僕は、scheme処理系として、gosh , kawa, petite-chez-scheme の三つを使っているのだが、 このうち、goshと petite にはデフォルトでdefmacroがない。
マクロ定義のソースファイルを処理系で同じにしたいので、goshとpetiteでの defmacroの定義方法を調べてみた。

goshでは define-macroを使って簡単にdefmacroらしきものが定義できた。
  (define-macro (defmacro name arglst . bodies)
    `(define-macro ,(cons name arglst) ,@bodies))

petiteには、define-syntax系の健全なマクロしかない。 syntax-caseというのを使えば何でも定義できそうだが、 syntax-case構文はなんだかとっても難しそうで僕の理解範疇を超えている。
幸いにも、petiteのダウンロードパッケージの中に、 examples/compat.ss というファイルがあり、この中に、 syntax-caseで定義したdefine-macroとdefmacroのソースをみつけた。 これをそのまま使えばなんとかなりそうだ。