2009年11月4日水曜日

cocoa emacs のインストール

leopardに、cocoa emacs (emacs23.1) をインストールした時のメモ。

INSTALL

インストールそのものは、 MacEmacs JP Project の 「IMEパッチの適用」に書いてあるコマンドを、そのまま実行すればうまくいく。
$ tar xvfz inline_patch-20090617.tar.gz
$ tar xvfz emacs-23.1.tar.gz
$ cd emacs-23.1
$ patch -p 0 < ../inline_patch-20090617/emacs-inline.patch
$ ./configure --with-ns --without-x
$ make bootstrap
$ make install
$ open nextstep/Emacs.app
ただし、これらの作業を、emacsのシェルモードで行うと、
 Error: charsets directory does not exist.
 ethiopic.el: Error: Failure in loading charset map: MULE-ethiopic
のエラーが出てコンパイルに失敗する。
たぶん、EMACS関連の環境変数が邪魔になっているのだろうと思い、 configure実行の前に、
$ unset EMACSAPP EMACSDATA EMACSPATH EMACSDOC INSIDE_EMACS EMACSLOADPATH
を実行すれば解決。もっとも、ターミナルで作業する分には問題ない。

フォント設定

フォントは、 ここ とか ここ を参考にして、意味もわからずに見様見真似で設定してみた。
こんな感じだ。
(create-fontset-from-fontset-spec
  "-*-*-medium-r-normal--14-*-*-*-*-*-fontset-hiramaru14" nil t)
(create-fontset-from-fontset-spec
  "-*-*-medium-r-normal--16-*-*-*-*-*-fontset-hiramaru16" nil t)
(create-fontset-from-fontset-spec
  "-*-*-medium-r-normal--20-*-*-*-*-*-fontset-hiramaru20" nil t)
(mapc
 #'(lambda (fontset)
     (set-fontset-font fontset 'japanese-jisx0208
                       '("Hiragino Maru Gothic Pro" . "iso10646-1"))
     (set-fontset-font fontset 'katakana-jisx0201
                       '("Hiragino Maru Gothic Pro" . "iso10646-1"))
     (set-fontset-font fontset 'japanese-jisx0212
                       '("Hiragino Maru Gothic Pro" . "iso10646-1"))
     ) (list "fontset-hiramaru20" "fontset-hiramaru16" "fontset-hiramaru14"))
(let (
      ;; (my-fontset "fontset-hiramaru14") ;; ちっちゃいフォント
      (my-fontset "fontset-hiramaru20") ;; でっかいフォント
     )
  (set-default-font my-fontset)
  (add-to-list 'default-frame-alist `(font . ,my-fontset)))
いちおう、これで、英数文字と日本語文字の幅が1対2になり、 carbon の時と同等にきれいに表示されるようになった。

IMの設定

.emacs.el に以下を追加すると、 かなキーでモードラインに「あU:」とかが表示されるようになる。
(set-language-environment "Japanese")
(setq default-input-method "MacOSX")
ただ、「ことえり」の場合は、 C-x o などでバッファーを切り替えた時に、 モードラインの表示が現在の変換モードと一致しなくなる。
そこで、コマンドループのフック( post-command-hook )に、 IMの更新をするコードを追加してみた。
(load "cl")
(add-hook
 'post-command-hook
 (lexical-let ((previous-buffer nil))
   #'(lambda ()
       (unless (eq (current-buffer) previous-buffer)
         ;; (message "Change IM %S -> %S" previous-buffer (current-buffer))
         (if (bufferp previous-buffer) (mac-handle-input-method-change))
         (setq previous-buffer (current-buffer))))))
無理矢理っぽいコードだが、とりあえずこれで、 バッファー切り替えでも違和感なく使えるようになった。

DocView

emacs の中でPDFがみられるという、 doc-view を試してみた。 でも、ディスク内のあらゆるPDFを試してもうまく表示されない。
emacs-23.1/lisp/doc-view.el をみてみると、Ghostscript と、 xpdf または teTeX が必要と書いてある。
さっそく port でインストール。
$sudo port install ghostscript
$sudo port install ghostscript-fonts-hiragino
$sudo port install xpdf
これでdoc-viewが動き、PDFが表示できるようになる。
でも、macでは、全てのpdfをdocviewで表示できるわけではないらしい。 見れるファイルもあるし見れないのもある。

.emacs.el

cocoa emacs用に、僕が使っている .emacs.el の抜粋。
いろいろなサイトに載っていた設定例の寄せ集めだが、 これでも、試行錯誤の連続で、落ち着くまでには結構苦労した。
(if (>= emacs-major-version 23)
    ;; cocoa emacsの設定
    (progn
      ;; Command キーをMetaキーにする
      (setq ns-command-modifier (quote meta))
      ;; (setq ns-alternate-modifier (quote super))
      (setq ns-alternate-modifier nil)

      ;; IM 設定
      (set-language-environment "Japanese")
      (setq default-input-method "MacOSX")
      (prefer-coding-system  'utf-8-unix)
      ;; minibufferは英数モードで始める
      (add-hook 'minibuffer-setup-hook 'mac-change-language-to-us)
      ;; buffer切り替えの時にも、IM状態をアップデート
      (load "cl")
      (add-hook
       'post-command-hook
       (lexical-let ((previous-buffer nil))
         #'(lambda ()
             (unless (eq (current-buffer) previous-buffer)
               ;; (message "Change IM %S -> %S" previous-buffer (current-buffer))
               (if (bufferp previous-buffer) (mac-handle-input-method-change))
               (setq previous-buffer (current-buffer))))))

      (define-key global-map [ns-drag-file] 'ns-find-file) ;; find-file

      ;; Set text scale key 
      (global-set-key "\C-c+" #'(lambda () (interactive) (text-scale-increase 1)))
      (global-set-key "\C-c-" #'(lambda () (interactive) (text-scale-decrease 1)))
      (global-set-key "\C-c0" #'(lambda () (interactive) (text-scale-increase 0)))

      ;; font 設定
      ;; (setq fixed-width-use-QuickDraw-for-ascii t)
      (setq mac-allow-anti-aliasing t)

      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--14-*-*-*-*-*-fontset-hiramaru14" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--16-*-*-*-*-*-fontset-hiramaru16" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--20-*-*-*-*-*-fontset-hiramaru20" nil t)
      (mapc
       #'(lambda (fontset)
           (set-fontset-font fontset 'japanese-jisx0208
                             '("Hiragino Maru Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'katakana-jisx0201
                             '("Hiragino Maru Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'japanese-jisx0212
                             '("Hiragino Maru Gothic Pro" . "iso10646-1"))
           ) (list "fontset-hiramaru20" "fontset-hiramaru16" "fontset-hiramaru14"))
      
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--14-*-*-*-*-*-fontset-hirakaku14" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--16-*-*-*-*-*-fontset-hirakaku16" nil t)
      (create-fontset-from-fontset-spec
       "-*-*-medium-r-normal--20-*-*-*-*-*-fontset-hirakaku20" nil t)
      (mapc
       #'(lambda (fontset)
           (set-fontset-font fontset 'japanese-jisx0208
                             '("Hiragino Kaku Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'katakana-jisx0201
                             '("Hiragino Kaku Gothic Pro" . "iso10646-1"))
           (set-fontset-font fontset 'japanese-jisx0212
                             '("Hiragino Kaku Gothic Pro" . "iso10646-1"))
           ) (list "fontset-hirakaku20" "fontset-hirakaku16" "fontset-hirakaku14"))

      (setq face-font-rescale-alist
            '(("^-apple-hiragino.*" . 1.2)
              (".*osaka-bold.*" . 1.2)
              (".*osaka-medium.*" . 1.2)
              (".*courier-bold-.*-mac-roman" . 1.0)
              (".*monaco cy-bold-.*-mac-cyrillic" . 0.9)
              (".*monaco-bold-.*-mac-roman" . 0.9)
              ("-cdac$" . 1.3)))

      (let ((my-fontset "fontset-hirakaku20")
            ;; (my-fontset "fontset-hiramaru20")
            )
        (set-default-font my-fontset)
        (add-to-list 'default-frame-alist `(font . ,my-fontset)))
      )
  
  ;; carbon emacs (emacs 22) の設定
  (progn
    (require 'carbon-font)
    ;; ...................................................
    ;; ...................................................
    ))

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のソースをみつけた。 これをそのまま使えばなんとかなりそうだ。