2010年8月28日土曜日

ClojureでJWindowを作る

ふせんちっくなウィンドウを作ってみます。

無駄にクリック時の動作をかえられるようにしたりしてみました。

;;; MouseAdapter

(ns example.MouseAdapter
(:gen-class
:extends java.awt.event.MouseAdapter
:init init
:state actionTable
:constructors {[clojure.lang.Atom][] [][]}
:main false
;; :expose-methods {mouseClicked mouseClicked}
:methods
[
[clickAction [java.awt.event.MouseEvent] void]
[doubleClickAction [java.awt.event.MouseEvent] void]
[rightClickAction [java.awt.event.MouseEvent] void]
]))

(defmacro call-action [action-type]
`(let [act# (get @(.actionTable ~'this) ~action-type)]
(when act#
(act# ~'evt))))


(defn -init ([action-table-atom] [[] action-table-atom])
([] (letfn ((dummy [evt] nil))
[[] (atom {:click-action dummy :double-click-action dummy :right-click-action dummy :press-action dummy
:drag-action dummy})])))

(defn -clickAction [this evt] (call-action :click-action))

(defn -doubleClickAction [this evt] (call-action :double-click-action))

(defn -rightClickAction [this evt] (call-action :right-click-action))

(defn -mouseClicked [this evt]
(cond
(= java.awt.event.MouseEvent/BUTTON3 (.getButton evt)) (.rightClickAction this evt)
(= java.awt.event.MouseEvent/BUTTON1 (.getButton evt))
(if (>= (.getClickCount evt) 2)
(.doubleClickAction this evt)
(.clickAction this evt))))

(defn -mousePressed [this evt]
(call-action :press-action))

(defn -mouseDragged [this evt]
(call-action :drag-action))

(defn alter-action [adapter type action]
(swap! (.actionTable adapter) assoc type action))

(defn set-action-table [adapter action-table]
(set! (.actionTable adapter) action-table))

(defn get-action-table [adapter]
@(.actionTable adapter))

(require 'example.MouseAdapter)

(defstruct item :name :object :listener)
(defstruct husen-window :window :label :adapter :popup :item-map :size)

(def *default-husen-size* (list 150 50))
(defn get-x [size] (first size))
(defn get-y [size] (second size))

(defmacro define-and-add-item [[popup item-map name text] [evt] & action-performer]
`(let [obj# (javax.swing.JMenuItem. ~text)
listener# (proxy [java.awt.event.ActionListener][]
(actionPerformed [~'evt]
~@action-performer))]
(.addActionListener obj# listener#)
(.add ~'popup obj#)
(swap! ~'item-map assoc ~'name (struct item obj# listener#))))


(defn make-husen-window []
(let [win (javax.swing.JWindow.)
adapter (atom (example.MouseAdapter.))
popup (javax.swing.JPopupMenu.)
item-map (atom nil)
lbl (javax.swing.JLabel. "")
size (atom *default-husen-size*)
point-map (atom {:location nil :press-point nil})]

(.setSize lbl (get-x @size) (get-y @size))
(.setVisible lbl true)
(.add (.getContentPane win) lbl)

(define-and-add-item [popup item-map :set-visible-item "setVisible(false)"]
[evt]
(.setVisible win false))
(define-and-add-item [popup item-map :set-text-item "Set Text"]
[evt]
(let [txt (javax.swing.JOptionPane/showInputDialog (.getContentPane win) "Input Text:")]
(when txt
(.setText lbl txt))))
(define-and-add-item [popup item-map :set-icon-item "Set Icon"]
[evt]
(let [chooser (javax.swing.JFileChooser.)]
(if (= javax.swing.JFileChooser/APPROVE_OPTION
(.showOpenDialog chooser (.getContentPane win)))
(let [icon (javax.swing.ImageIcon. (.. chooser getSelectedFile getAbsolutePath))]
(if icon
(.setIcon lbl icon)
(.setText lbl (.. chooser getSelectedFile getName)))))))
(define-and-add-item [popup item-map :set-default-size "Set Default Size"]
[evt]
(.setSize win (get-x @size) (get-y @size))
(.setSize lbl (get-x @size) (get-y @size)))
(define-and-add-item [popup item-map :set-icon-size "Set Icon Size"]
[evt]
(let [icon (.getIcon lbl)
height
(if (isa? (.getClass icon) javax.swing.ImageIcon)
(.getHeight (.getImage icon))
(.getIconHeight icon))
width
(if (isa? (.getClass icon) javax.swing.ImageIcon)
(.getWidth (.getImage icon))
(.getIconWidth icon))]
(.setSize win width height)
(.setSize lbl width height)))
(example.MouseAdapter/alter-action
@adapter
:right-click-action
(fn [evt]
(.show popup (.getComponent evt) (.getX evt) (.getY evt))))
(example.MouseAdapter/alter-action
@adapter
:press-action
(fn [evt]
(swap! point-map assoc :press-point (.getPoint evt))))
(example.MouseAdapter/alter-action
@adapter
:drag-action
(fn [evt]
(swap! point-map assoc :location (.getLocation win))
(let [{p :press-point loc :location} @point-map
x (-
(+ (.x loc)
(.getX evt))
(.getX p))
y (- (+ (.y loc) (.getY evt)) (.getY p))]
(.setLocation win x y))))
(doto win
(.addMouseListener @adapter)
(.addMouseMotionListener @adapter)
(.setSize (get-x @size) (get-y @size))
(.setVisible true))
(struct husen-window win lbl adapter popup item-map size)))

Clojureのメソッドチェイン風マクロをCommon Lispのマクロで書いてみる

すでにいろいろなところで書かれているネタな気はしますが、書いてみました。

->はexpを次の式の第一引数の位置に挿入し、 ->>はexpを次の式の最後の引数の位置に挿入します。

(defmacro -> (exp &rest rest)
(if rest
(let ((fst (car rest))
(rest (cdr rest)))
(typecase fst
(symbol `(-> (,fst ,exp) ,@rest))
(atom `(-> (,fst ,exp) ,@rest))
(list `(-> (,(car fst) ,exp ,@(cdr fst)) ,@rest))))
exp))

(defmacro ->> (exp &rest rest)
(if rest
(let ((fst (car rest))
(rest (cdr rest)))
(typecase fst
(symbol `(->> (,fst ,exp) ,@rest))
(atom `(->> (,fst ,exp) ,@rest))
(list `(->> (,(car fst) ,@(cdr fst) ,exp) ,@rest))))
exp))

Clojureでバイナリファイルを読み込む

実践Common Lisp(Practical Common Lisp)のバイナリファイルのパースっぽいものを書こうとしてみました。

(defmulti read-binary-class (fn [class in] class))
(defmulti read-binary-raw (fn [class in] class))

(def *class-list* (atom []))
(def *direct-super-classes-map* (atom {}))

(defn defined-class-p [sym]
(some #(= sym %1) @*class-list*))

;; Symbol like <xxx> means forward declaration
(defn forward-declaration-symbol-p [sym]
(let [tmp (str sym)
len (count tmp)]
(if (<= len 2)
false
(and (= \< (first tmp)) (= \> (last tmp))))))

(defn deref-forward-declaration-symbol [sym map]
`(get ~map '~(symbol (subs (str sym)
1
(dec (count (str sym)))))))

(defn deref-forward-declaration-symbol-recur [obj m]
(cond
(symbol? obj)
(if (forward-declaration-symbol-p obj)
(deref-forward-declaration-symbol obj m)
obj)
(list? obj) (map
#(deref-forward-declaration-symbol-recur %1 m)
obj)
(number? obj) obj
true obj))

(defn make-binary-object
([name]
{:binary-class-name name})
([name map]
(assoc map :binary-class-name name )))

(defn get-direct-super-classes [name]
(get @*direct-super-classes-map* name))

(defmacro with-map [map bind & body]
`(let [~bind ~map]
~@body))

(defn expand-clause-for-reader [clause in]
(if (list? (second clause))
;; (second clause) = (:list class expr)
(if (= (first (second clause)) :list)
;; (:list class expr) expr -> length
(let [[_ class expr] (second clause)
m (gensym "map")]
`(with-map ~m
(assoc ~m
'~(first clause)
(doall
(for [_# (range ~(deref-forward-declaration-symbol-recur expr m))]
~(if (defined-class-p class)
`(~'read-binary-class '~class ~in)
`(~'read-binary-raw '~class ~in)))))))
;; return class name (symbol) expr
(let [m (gensym "map")]
`(with-map ~m
(assoc ~m
'~(first clause)
(let [sym# ~(deref-forward-declaration-symbol-recur (second clause) m)]
(if (~'defined-class-p sym#)
(~'read-binary-class sym# ~in)
(~'read-binary-raw sym# ~in)))))))
`(assoc '~(first clause)
~(if (defined-class-p (second clause))
`(~'read-binary-class '~(second clause) ~in)
`(~'read-binary-raw '~(second clause) ~in)))))



(defn expand-clauses-for-reader [m clauses in]
`(-> ~m
~@(map (fn [clause]
(expand-clause-for-reader clause in))
clauses)))

(defn expand-read-binary-body [name in clauses]
(let [var (gensym "var")
class (gensym "class")
tmp (gensym "tmp")]
`(let [super-classes# (get-direct-super-classes '~name)
~tmp (apply merge
(map
(fn [super#]
(~'read-binary-class super# ~in))
super-classes#))]
~(expand-clauses-for-reader tmp clauses in))))

(defmacro def-binary-raw [name reader]
`(do
(defmethod read-binary-raw '~name ~@reader)))

;; clause = (name class)
(defmacro def-binary-class [name [& supers] & clauses]
(let [obj (gensym "obj")
in (gensym "in")
out (gensym "out")
classname (gensym "classname")]
`(do
(swap! *class-list* conj '~name)
(swap! *direct-super-classes-map* assoc '~name '~supers)
(defmethod read-binary-class '~name [~classname ~in]
~(expand-read-binary-body name in clauses)))))
;;; examples

(def-binary-raw u1
;; reader
([type in]
(bit-and 255 (.read in))))

;; big endian
(def-binary-raw u2
;; reader
([type in]
(+ (bit-shift-left (bit-and 255 (.read in)) 8)
(bit-and 255 (.read in)))))

(def-binary-raw u4
;; reader
([type in]
(+ (bit-shift-left (bit-and 255 (.read in)) 24)
(bit-shift-left (bit-and 255 (.read in)) 16)
(bit-shift-left (bit-and 255 (.read in)) 8)
(bit-and 255 (.read in)))))

;;; java class file format
;; tagはcp-infoが保持し、cp-infoのinfo部にtagに応じた
;; クラスの値が入るようにする

(def-binary-class constant-class-info []
(name-index u2))
(def-binary-class constant-fieldref-info []
(class-index u2)
(name-and-type-index u2))

(def-binary-class constant-methodref-info []
(class-index u2)
(name-and-type-index u2))
(def-binary-class constant-interface-methodref-info []
(class-index u2)
(name-and-type-index u2))
(def-binary-class constant-string-info []
(string-index u2))

(def-binary-class constant-integer-info []
(bytes u4))
(def-binary-class constant-float-info []
(bytes u4))

(def-binary-class constant-long-info []
(high-bytes u4)
(low-bytes u4))
(def-binary-class constant-double-info []
(high-bytes u4)
(low-bytes u4))

(def-binary-class constant-name-and-type-info []
(name-index u2)
(descriptor-index u2))

(def-binary-class constant-utf8-info []
(length u2)
(bytes (:list u1 <length>)))


(def-binary-class cp-info []
(tag u1)
(info (case <tag>
7 'constant-class-info
9 'constant-fieldref-info
10 'constant-methodref-info
11 'constant-interface-methodref-info
8 'constant-string-info
4 'constant-integer-info
3 'constant-float-info
5 'constant-long-info
6 'constant-double-info
12 'constant-name-and-type-info
1 'constant-utf8-info)))

(def-binary-class attribute-info []
(attribute-name-index u2)
(attribute-length u4)
(info (:list u1 <attribute-length>)))

(def-binary-class method-info []
(access-flag u2)
(name-index u2)
(descriptor-index u2)
(attributes-count u2)
(attributes (:list attribute-info <attributes-count>)))

(def-binary-class field-info []
(access-flags u2)
(name-index u2)
(descriptor-index u2)
(attributes-count u2)
(attributes (:list attribute-info <attributes-count>)))


(def-binary-class jvm-class-file []
(magic u4)
(minor-version u2)
(major-version u2)
(constant-pool-count u2)
(constant-pool (:list cp-info (- <constant-pool-count> 1)))
(access-flags u2)
(this-class u2)
(super-class u2)
(interfaces-count u2)
(interfaces (:list u2 <interfaces-count>))
(fields-count u2)
(fields (:list field-info <fields-count>))
(methods-count u2)
(methods (:list method-info <methods-count>))
(attributes-count u2)
(attributes (:list attribute-info <attributes-count>)))


(defn test-read-binary-class [class fname]
(with-open [in (java.io.FileInputStream. fname)]
(read-binary-class class in)))

(use 'clojure.contrib.trace)
(defn test-read-binary-class-with-trace [class fname]
(dotrace [read-binary-class read-binary-raw]
(with-open [in (java.io.FileInputStream. fname)]
(read-binary-class class in))))

2010年8月26日木曜日

符号的プログラミングのすすめ on Common Lisp

初めに

Lispの括弧ネタ(注1)に触発されて思い浮かんだネタを書いていきます。

注1: @nitro_idiotさん http://e-arrows.sakura.ne.jp/2010/08/is-lisp-really-has-too-many-parenthesis.html

符号的プログラミングについて

Perlは非常にリッチな言語です。世界はPerlでかかれているらしいです。Perl最強ですね。

このPerl言語のエキスパートたちが、Perlの持つリッチな機能をフル活用するプログラミングスタイルを符号的プログラミングと呼びます。

Perlには及ばないかもしれませんが、私の大好きなCommon Lispも非常にリッチな言語なため、符号的プログラミングを行うことができます。

これから、一般的なスタイルのCommon Lispプログラムをいかにして符号的なスタイルに変換していくかを見ていきましょう。

一般的なスタイルのCommon Lispプログラム

元ネタに合わせて、階乗の値を順番に表示していくプログラムを書いてみます。

(defun fact-1 (to)
(labels ((inner (n acc)
(when (<= n to)
(format t "~A! = ~A~%" n acc)
(inner (1+ n) (* (1+ n) acc)))))
(inner 1 1)))

(fact-1 20)

関数fact-1の内部で、関数innerを定義し、再帰呼び出しを行っています。

この関数を徐々に符号的に改良していきます。

名前は付けない、使わない

fact-1を眺めて、まず気づく問題点は内部関数に名前を付けている点です。 Common Lispには無名関数を作るlambdaマクロ(注2)があるので、これを利用しましょう。

注2: 関数呼び出しの位置にlambdaフォームがくると特別扱いされるので、他のシンボルより特殊な存在だと思います。

(defun fact-2 (to)
((lambda (fn n acc)
(when (<= n to)
(format t "~A! = ~A~%" n acc)
(funcall fn fn (1+ n) (* (1+ n) acc))))
(lambda (fn n acc)
(when (<= n to)
(format t "~A! = ~A~%" n acc)
(funcall fn fn (1+ n) (* (1+ n) acc))))
1 1))

関数に名前を付けることは避けられましたが、今度はまったく同じ式を2回も記述しなければならないという問題が発生しました。関数(サブルーチン)にも言えますが、同じものはまとめてしまうのが普通でしょう。

しかし、このlambdaフォームをまとめるために名前を付けてしまっては本末転倒です。そこで、共有構造を利用することにしましょう。

(defun fact-3 (to)
(#1=(lambda (fn n acc)
(when (<= n to)
(format t "~A! = ~A~%" n acc)
(funcall fn fn (1+ n) (* (1+ n) acc))))
#1#
1 1))

これで見やすくなりました。

せっかくなので、defunもlambdaに直してみましょう。

(set 'fact-4
(lambda (to)
(#1=(lambda (fn n acc)
(when (<= n to)
(format t "~A! = ~A~%" n acc)
(funcall fn fn (1+ n) (* (1+ n) acc))))
#1#
1 1)))

setはマクロではなく関数です。第1引数のシンボルに、第2引数引数の値をセットします。 setは関数スロットではなく普通の値を格納するスロットに第2引数をセットするため、関数呼び出しにはfuncallが必要となります。

> (funcall fact-4 20)

さて、本題に戻ります。内部関数の名前は消え去りましたが、この関数にはまだまだ名前がたくさん残っています。

まずは関数名から消していきましょう。関数は単純に他のシンボルにセットすればいいだけなので簡単です。

(setf (symbol-function '@) #'funcall)
(setf (symbol-function '~) #'format)
(set 'fact-5
(lambda (to)
(#1=(lambda (fn n acc)
(when (<= n to)
(~ t "~A! = ~A~%" n acc)
(@ fn fn (1+ n) (* (1+ n) acc))))
#1#
1 1)))

(@ fact-5 20)

次は変数名を消していきましょう。先ほどから何度か話題に出ていましたが、 Common Lispのシンボルには値をセットするスロットが複数あるので、関数と通常の変数で同じシンボルを別々の意味で利用できます。

(defparameter & t)
(set 'fact-6
(lambda (>)
(#1=(lambda (@ - *)
(when (<= - >)
(~ & "~A! = ~A~%" - *)
(@ @ @ (1+ -) (* (1+ -) *))))
#1#
1 1)))

whenはマクロなので、setfで設定できません(多分)

なので、他のマクロでラップしてしまいましょう。

(defmacro  ? (&rest args)
`(when ,@args))
(set 'fact-7
(lambda (>)
(#1=(lambda (@ - *)
(? (<= - >)
(~ & "~A! = ~A~%" - *)
(@ @ @ (1+ -) (* (1+ -) *))))
#1#
1 1)))

lambdaと括弧を符号的にする

lambdaも同じように対処できるかと思いきや、「注2」に書いたように、関数呼び出し位置に現れるlambdaフォームは特別扱いされるため、うまくいきません。

;; 例
((lambda (x y) (list x y)) 2 3)
-> (2 3)
(defmacro my-lambda (&rest args)
`(lambda ,@args))
(macroexpand-1 '(my-lambda (x y) (list x y)))
->(LAMBDA (X Y) (LIST X Y))
((my-lambda (x y) (list x y)) 2 3)





この問題を解決するためには、自分の定義したシンボルが読み込みんむタイミングでlambdaに変化してくれれば良さそうです。

Common Lispでは、実行時、コンパイル時(≒マクロ)の他に、読み込み時の動作を定義するリーダマクロが存在します。

リーダマクロを利用すれば、問題を解決できるに違いありません。

(set-macro-character
#\^
#'(lambda (stream char)
(declare (ignore char))
`(lambda ,@(read-delimited-list #\) stream t))))
(set 'fact-8
^ (>)
(#1=^ (@ - *)
(? (<= - >)
(~ & "~A! = ~A~%" - *)
(@ @ @ (1+ -) (* (1+ -) *))))
#1#
1 1)))

ここまで書くとふと思います。この括弧の群れは符号的ではないのではないか、と。

消し去ってやりましょう。

(set-macro-character
#\!
#'(lambda (stream char)
(declare (ignore char))
(read-delimited-list #\$ stream t)))
(set-macro-character
#\^
#'(lambda (stream char)
(declare (ignore char))
`(lambda ,@(read-delimited-list #\$ stream t))))

! set 'fact-9
^ ! > $
! #1= ^ ! @ - * $
! ? ! <= - > $
! ~ & "~A! = ~A~%" - * $
! @ @ @ ! 1+ - $ ! * ! 1+ - $ * $ $ $ $
#1#
1 1 $ $ $

ここまでくれば後一歩です。最後に残った関数名、fact-nを取り去り、直接引数を与えて呼び出してみましょう。

! @ ^ ! > $
! #1= ^ ! @ - * $
! ? ! <= - > $
! ~ & "~A! = ~A~%" - * $
! @ @ @ ! 1+ - $ ! * ! 1+ - $ * $ $ $ $
#1#
1 1 $ $
20 $

終わりに

最初と最後のプログラムを比べると、もはや別のプログラミング言語ではないかと思えてしまいます。しかし、これはどちらも同じCommon Lispプログラムなのです。

符号的プログラミングは、一般的なプログラミングスタイルとはかけ離れているように見え、一部のエキスパートにしか駆使することの出来ない黒魔術かのような錯覚を覚えますが、一つ一つの要素を抜き出して考えれば、私たちが日頃書いているごく普通のプログラムとかわりはありません。

Perlは非常に高機能ですが敷居が高いのが難点です。その点、Common Lispは一般的なスタイルのプログラムも非常に書きやすいので、符号的プログラミング入門者にもおすすめです。

Perlを極めた人も、これからプログラミングを始める人も、ぜひ一度Common Lispで遊んでみてください。

文体

文体をですます風味に変更してみます.こっちのほうが適当っぽさがでるので.

2010年8月19日木曜日

Emacsで指定した正規表現が現れる場所までkillする

C-hやC-dを連打するのに疲れたので、タイトルどおりのEmacs Lisp関数を作ってみた。 M-dなどをうまく駆使すれば連打の必要はなかったのかもしれないけれど。

実は組み込みで求めている機能がある、なんてことはないと信じたい。

(defun kill-to-regexp-forward (regexp)
(interactive "sRegexp:")
(let ((start-point (point)))
(when (re-search-forward regexp nil t)
(re-search-backward regexp nil t)
(kill-region start-point (point)))))

(defun kill-to-regexp-backward (regexp)
(interactive "sRegexp:")
(let ((start-point (point)))
(when (re-search-backward regexp nil t)
(re-search-forward regexp nil t)
(kill-region (point) start-point))))

現状、正規表現にマッチした部分は切り取らないようにしている。使ってみて、マッチした部分も消したほうが便利そうなら変更しようと思う.

2010年8月17日火曜日

旅行履歴(2010/08/11 - 2010/08/15)

青春18切符で京都と長野に行ってきました。とりあえず記録を残しておこう。

  • 1日目
    • 京都
    • 夕食 錦魚亭
    • 飲み 酒Bar よらむ
    • 飲んだ酒
      • 麒麟 時醸酒
      • 十酒(とき) 1988
      • 開春 純米蝶辛口
      • 舞美人 純米 常温生熟 おりがらみ
      • 能登 3年酒
    • 宿泊:FIRST CABIN
  • 2日目
    • 京都
    • 八坂神社
    • 平安神宮 お神酒(橘酒)購入
    • 吉田山 大文字の形は見えず
    • 伏見稲荷大社
    • 飲み たかはし (日本酒酒バー)
    • 飲んだ酒
      • 竹鶴(日本酒) 純米原酒
      • 出置桜 強力 純米
      • 竹鶴 純米吟醸 古酒
      • 勝駒
    • 宿泊:FIRST CABIN
  • 3日目
    • 京都 -> 長野(岡谷)
    • 岡谷太鼓祭り
    • 宿泊:岡谷セントラルホテル
  • 4日目
    • 長野
    • 諏訪大社 春宮、秋宮、前宮、本宮
    • 北斗神社。本宮の近くの急な階段を昇ったところ。
    • 日本酒購入@二葉屋酒店: 黒松仙醸 寒造り純米 新春仕込み だるま市しぼり(低温熟成)
    • 昼食:本宮の近くの蕎麦屋
    • 夕食:御うな 小松屋 の 御うなまぶし
    • 宿泊:岡谷セントラルホテル
  • 5日目
    • 帰路

日本酒の古酒にときめいた。

冷やし飴を始めて飲んだが、ショウガの風味があるやつで結構おいしかった。

2010年8月4日水曜日

本日の酒(2010/08/04)

本日の酒は、はるばるドイツからやってきたビール、ヴァルシュタイナー。

輸入してるのはアイコン・ユーロパブ株式会社というとこらしい。

2010年8月3日火曜日

McCLIMで升目を描く

formatting-tableを利用すれば良さそうだけど、無理やりな感じが現れてる。

LispworksのCLIMのページを参考にした。

(asdf:oos 'asdf:load-op :mcclim)
;;(asdf:oos 'asdf:load-op :mcclim-truetype)
(in-package :clim-user)

(defun output-table (&key (stream *standard-output*)
inter-row-spacing
inter-column-spacing)
(clim:formatting-table
(stream :x-spacing inter-row-spacing
:y-spacing inter-column-spacing)
(dotimes (i 3)
(clim:formatting-row
(stream)
(dotimes (j 3)
(clim:formatting-cell
(stream)
(clim:draw-rectangle* stream 10 10 50 50 :filled nil)))))))

(define-application-frame formatting-test-frame
()
()
(:menu-bar t)
(:panes
(app-pane :application
:min-width 150
:min-height 150
:scroll-bar t
:display-time :command-loop
:display-function #'(lambda (frame stream)
(output-table :stream stream :inter-row-spacing '(0 :line) :inter-column-spacing '(0 :line))))
)
(:layouts
(default (horizontally () app-pane))))

(define-formatting-test-frame-command (com-quit :menu t) ()
(frame-exit *application-frame*))

;;(run-frame-top-level (make-application-frame 'formatting-test-frame))