Skip to content
2011/05/28 / highmt

clojureで名前空間つきリードマクロをつくる

環境: Clojure 1.2.0

お約束ですが、やっちゃいけません。

実際、Paul Graham がリードマクロの利点のひとつとしてあげている
read が再帰的に処理される、という例
(http://www.komaba.utmc.or.jp/~flatline/onlispjhtml/readMacros.html)

user=> ''a
(quote a)

は、自分でマクロ展開すれば実現できますし、

user=> (defmacro q [form] (list 'quote (macroexpand form)))
#'user/q
user=> (q (q a))
(quote a)

ほんとうにリード時の処理が必要!ってなる場面は少ないような気がします。

ユーザー定義リードマクロが好ましくないとする理由はいろいろあるようですが、
ひとつに名前空間のサポートがない、ということがあります。
(http://clojure-log.n01se.net/date/2008-11-06.html)

ということで名前空間つきリードマクロ(namespaced reader macro)を
試みてみました。

これって cl-annot な方向なのかも…

;;;
;;; (ns rdm
;;;   (:require clojure.contrib.def))
;;;

(defn dispatch-reader-macro
  "see:
  [http://briancarper.net/blog/449/clojure-reader-macros
   http://rd.clojure-users.org/entry/view/56001]"
  [ch fun]
  (let [dm (.get (doto (.getDeclaredField clojure.lang.LispReader "dispatchMacros")
                   (.setAccessible true))
                 nil)]
    (aset dm (int ch) fun)))

(defn true-sym
  "a helper function to treat namespace aliases."
  [ns sym]
  (symbol (str (if-let [t-ns ((ns-aliases *ns*) ns)] t-ns ns))
          (str sym)))

(defn find-true-var
  "a helper function to treat var renamings."
  [sym]
  (if-let [ns (.getNamespace sym)]
    (find-var (true-sym (symbol ns)
                        (symbol (.getName sym))))
    ((ns-map *ns*) sym)))

(defn get-ns-reader-macro-fun
  "a helper function to get a var of the readermacro."
  [form]
  (cond
   (symbol? form) (find-true-var form)
   (list? form) (recur (first form))
   :else (throw (Exception. (str "read error: " form)))))

(defn get-ns-reader-macro-args
  "a helper function to get args of the readermacro."
  [form]
  (cond
   (symbol? form) nil
   (list? form) (rest form)
   :else (throw (Exception. (str "read error: " form)))))

(defn read-form
  "a helper function to read a form following the readermacro."
  [rdr]
  (clojure.lang.LispReader/read rdr true nil true))

(defn dispatch-ns-reader-macro
  "a helper function to apply a var of the readermacro to args of it."
  [rdr letter-sharp]
  (let [rmsym (read-form rdr)
        rmfun (get-ns-reader-macro-fun rmsym)
        rmargs (get-ns-reader-macro-args rmsym)]
    (if rmfun
      (apply rmfun rdr rmsym rmargs)
      (throw (Exception. (str "reader macro not defined: " rmsym))))))

(dispatch-reader-macro \# dispatch-ns-reader-macro)

(defmacro defreadermacro
  "defines the readermacro.
usage:
  ##name form
  ##(name & args) form
name can be ns/name."
  {:arglists '([name args & body])}
  [name & macro-args]
  (let [[mname# margs#] (clojure.contrib.def/name-with-attributes name macro-args)]
  `(defn ~mname# ~margs#)))


;;;
;;; client
;;;

(defreadermacro uppercase-string
  "makes string uppercase."
  [rdr sym & args]
  (let [c (.read rdr)]
    (if (= c (int \"))
      (.toUpperCase (.invoke
                     (clojure.lang.LispReader$StringReader.)
                     rdr
                     c))
      (throw (Exception. (str "read error: " (char c) args))))))
;;user> ##uppercase-string"a"
;;"A"

(defreadermacro my-quote
  "simulates quote."
  [rdr sym & args]
  (let [form (read-form rdr)]
    (list 'quote form)))
;;user> ##my-quote ##my-quote a
;;(quote a)

(defreadermacro my-wrap
  "something a little more complicated."
  [rdr sym & args]
  (let [form (read-form rdr)
        wrapper (first args)]
    (if (list? wrapper)
      `(~@wrapper ~form)
      `(~wrapper ~form))))

;;user> ##(my-wrap (let [x 1])) (+ x 1)
;;2
;;user=> (in-ns 'user2)
;;#<Namespace user2>
;;user2=> (clojure.core/refer-clojure)
;;nil
;;user2=> ##(user/my-wrap (let [x 1])) (+ x 1)
;;2
;;user2=> (alias 'user1 'user)
;;nil
;;user2=> ##(user1/my-wrap (let [x 1])) (+ x 1)
;;2
;;user2=> (refer 'user :rename {'my-wrap 'wrap})
;;nil
;;user2=> ##(wrap (let [x 1])) (+ x 1)
;;2

read-form 以外にもいろんな read-* を提供するようにすれば
(で binding で rdr 隠すとかして呼びやすくすれば)
そんなにわかりにくくない形でユーザー定義リードマクロを導入できるのかも。

ちなみに最初はリードマクロの実体にマルチメソッドを使って
varを作らないようにしていたのですが
varをつくらないと refer, rename の解決ができない
(というかそもそも refer の対象にすらならない)
ということが判明しあえなく却下。
refer ってシンボル間の関係じゃなくて var へのマッピングを追加するんだ
ということをあらためて思い知らされました。

広告
2011/05/11 / highmt

続 clojureで継続モナドを使う

前回の続きです。
stateモナド変換子を使ってもうちょっとすっきりさせてみました。

(use '[clojure.contrib.monads])

(defn pass
  ([]
     (pass nil))
  ([v]
     (fn [s]
       (with-monad cont-m
         (m-result [v s]))))) 

(defn fail []
  (fn [s]
    (if s (s) ((pass) s))))

(defn choose-next [cc s]
  (if s
    (s)
    (cc [nil nil])))

(defn choose
  ([choices]
     (fn [s]
       (with-monad cont-m
         (call-cc
          (fn [cc] (choose cc choices s))))))
  ([cc choices s]
     (if (empty? choices)
       (choose-next cc s)
       (cc [(first choices) #(choose cc (next choices) s)]))))

(defn run-choose [m]
  (run-cont
   (m (fn []
        (with-monad cont-m
          (m-result [nil nil]))))))

(defn test-state-t []
  (run-choose
   (domonad (state-t cont-m)
     [v1 (choose [1 2 3])
      v2 (choose [7 8 9])
      v  (if (or (some nil? [v1 v2]) (odd? (* v1 v2)))
           (fail)
           (pass [v1 v2]))]
     v)))

;; example:
;; user=> (test-state-t)
;; [[1 8] #<user$choose$fn__1554 user$choose$fn__1554@55104da7>]
;; user=> (run-cont ((fnext *1)))
;; [[2 7] #<user$choose$fn__1554 user$choose$fn__1554@1c6745b9>]
;; user=> (run-cont ((fnext *1)))
;; [[2 8] #<user$choose$fn__1554 user$choose$fn__1554@430a14ad>]
;; user=> (run-cont ((fnext *1)))
;; [[2 9] #<user$choose$fn__1554 user$choose$fn__1554@67afe460>]
;; user=> (run-cont ((fnext *1)))
;; [[3 8] #<user$choose$fn__1554 user$choose$fn__1554@28fe53cf>]
;; user=> (run-cont ((fnext *1)))
;; [nil nil]

このへんになると型チェック機構がないとかなり厳しい感じがします…。
なんとかならないかなー。

ただ、いざ問題が起きたときにコンパイル時エラーと実行時エラーとどっちが問題を調べやすいか、というと、
場合によっては実行時エラーのほうが気が楽なこともあるかもしれないです。
コンパイル時の動作にトレース入れたりとかは実行時に比べると敷居が高い感じがするので…。

2011/05/08 / highmt

clojureで継続モナドを使う

環境:
Clojure 1.2.0

非決定性を扱うにはいろいろな選択肢がありますが、そのひとつとして継続での実装があると思います。
clojureで継続を使うには On Lisp 風の継続渡しマクロを使った実装などが考えられますが、
今回は継続モナドの使い方のサンプルもかねて継続モナドで choose / fail を書いてみました。
連続して失敗する選択肢が多いとスタックが溢れるので実用にはなりません。
工夫したらなんとかなるんだろうか…。
陽に restart を返してたりとかあんまり継続を使うメリットいかせてないし…。
新しくモナド書いてうまく隠せばいけそうな気もするけど今の力ではとても大掛かりな感じがするし…。
そもそもモナドを使うのであれば素直にシーケンスとかを使ったほうが
m-zeroとか使ってもっとシンプルに書けるし対角化シーケンスとかの応用もできるしよさげです多分。

(use '[clojure.contrib.monads])

(defn choose
  "Chooses one from specified choices.
  Returns a function to try the next choice for each choices."
  ([choices]
     (with-monad cont-m
       (call-cc
        (fn [cc]
          (m-result (choose cc choices))))))
  ([cc choices]
     (if (empty? choices)
       [nil nil]
       [(first choices) #(cc (choose cc (next choices)))])))

(defn pass
  "Falls through to pass the choice."
  ([] (pass nil))
  ([v] (with-monad cont-m (m-result v))))

(defn merge-restarts
  "Calls restart to go back to the next choice."
  [restarts]
  #(if-let [restart (some identity (reverse restarts))]
     (restart)
     (pass true)))

(defn test-choose
  "Gets choices from the function 'choose' and returns a passed result.
  The returned result contains a function to try the next choice."
  []
  (run-cont (domonad cont-m
              [[v1 restart1 :as vr1] (choose [1 2 3])
               [v2 restart2 :as vr2] (choose [5 6 7])
               :let [v (if (not-any? nil? [v1 v2]) (* v1 v2))
                     fail (merge-restarts [restart1 restart2])]
               ;; sample test
               eos (if (or (nil? v) (odd? v))
                     (fail)
                     (pass))]
              ;; passed result
              [[v1 v2 v] (if-not eos fail)])))

(defn do-test-choose []
  "Collects results from the function 'test-choose' and returns lazy-seq of them."
  (letfn [(step [[v restart]]
            (lazy-seq
             (if restart (cons v (step (run-cont (restart)))) nil)))]
    (step (test-choose))))

;;; get lazy-seq
(do-test-choose)
;; user=> (do-test-choose)
;; ([1 6 6] [2 5 10] [2 6 12] [2 7 14] [3 6 18])

;;; or step manually in repl
(test-choose)
;; user=> (test-choose)
;; [[1 6 6] #<user$choose$fn__917 user$choose$fn__917@6155035a>]
(run-cont ((fnext *1)))
;; user=> (run-cont ((fnext *1)))
;; [[2 5 10] #<user$choose$fn__917 user$choose$fn__917@72a60191>]
;; user=> (run-cont ((fnext *1)))
;; [[2 6 12] #<user$choose$fn__917 user$choose$fn__917@41697023>]
;; ...

2011/04/16 / highmt

emacs で javascript を整形する

環境:
GNU Emacs 23.2.1 (i386-mingw-nt6.1.7601)

emacs上での javascript 編集環境といえば js2-mode なわけですが、
整形コマンドが見当たりません。

というわけで以下で。
といっても
javaプロパティファイルを変換するconf-javaprop-mode拡張
とおなじことをやるだけです。
なので、タイトルに偽りがあって emacs から 単に整形コマンドを呼ぶだけです。
おなじことやるんならマクロかけという話もあるんですが手抜きです。情けない。

  1. jsbeautifierを入手。
    https://github.com/einars/js-beautify
  2. jsbeautifier.pyを呼ぶスクリプトをつくる。
    自分のemacsは、
    (setq shell-file-name “sh”) な環境なので、
    な環境なので、

    #!/bin/sh
    python c:/js-beautify/jsbeautifier.py "$@"
    

    とか。

  3. js-beautify を emacs から呼び出す本体をつくる。
    ;;; js-beautify.el
    
    (defcustom js-beautify-jsbeautifier-path "jsbeautifier"
      "js-beautify jsbeautifier command path."
      :type 'string
      :group 'js-beautify)
    
    (defcustom js-beautify-jsbeautifier-option "-i"
      "js-beautify jsbeautifier command option."
      :type 'string
      :group 'js-beautify)
    
    (defvar js-beautify-mode-map
      (let ((m (make-sparse-keymap)))
        (define-key m "\C-cc" 'js-beautify-convert-buffer)
        (define-key m "\C-cr" 'js-beautify-convert-region)
        m))
    
    
    (defun js-beautify-convert-region-to-1 (rev start end buff)
      (let ((command (concat js-beautify-jsbeautifier-path " " js-beautify-jsbeautifier-option)))
        (save-excursion
          (shell-command-on-region start end command buff nil "*js-beautify-error*" t))))
    
    (defun js-beautify-convert-region-to (rev start end buff)
      (let ((coding-system-for-read 'japanese-shift-jis-dos)
            (coding-system-for-write 'japanese-shift-jis-unix))
        (js-beautify-convert-region-to-1 rev start end buff)))
    
    (defun js-beautify-convert-region-to-buffer (rev start end)
      (interactive "P\nr")
      (js-beautify-convert-region-to rev start end (get-buffer-create "*js-beautify*")))
    (defun js-beautify-convert-buffer-to-buffer (rev)
      (interactive "P")
      (js-beautify-convert-region-to-buffer rev (point-min) (point-max)))
    
    (defun js-beautify-convert-region (rev start end)
      (interactive "P\nr")
      (js-beautify-convert-region-to rev start end t))
    (defun js-beautify-convert-buffer (rev)
      (interactive "P")
      (js-beautify-convert-region rev (point-min) (point-max)))
    
    (add-hook 'js2-mode-hook
              (lambda ()
                (let ((m (copy-keymap js-beautify-mode-map)))
                  (set-keymap-parent m js2-mode-map)
                  (use-local-map m))))
    
    (provide 'js-beautify)
    

    一応revが指定できるみたいに書いてありますが rev はなんの役割も果たしてません。
    前回のjprop.elをそのまま使って手抜きしてるだけです。
    自分の環境は (setq default-process-coding-system ‘(utf-8-dos . japanese-shift-jis-unix ))
    というちょっと変則的な環境なのでエンコーディングを明示してたりします。
    あと、js2-modeを前提としてたりします。

とここまで書いて、こんなおおげさなことをしなくても
M-| で ふつうに shell-command-on-region で jsbeautifier を呼べばいいんだよなー
と思ったのは内緒です。windows はパスとかいろいろ面倒なんです。

2011/04/08 / highmt

javaプロパティファイルを変換するconf-javaprop-mode拡張

環境:GNU Emacs 23.2.1 (i386-mingw-nt6.1.7601)

たいしたもんじゃありませんが
eclipseを立ち上げるまでもないちょっとした確認をしたいときに…

(require ‘jprop) すれば、 conf-javaprop-mode に
\C-cc:バッファ全体を日本語へ変換した結果を*jprop*バッファに出力する
\C-cr:選択範囲を日本語への変換結果を*jprop*バッファに出力する
などが追加されます。
プレフィクスつければ逆になります。

あとは jprop-native2ascii-path を変更するなり
jprop-mode-map を変更するなり
適当にアドバイズするなり
全面的に書き換えるなり。

;;; jprop.el

(defcustom jprop-native2ascii-path
  (let ((command "native2ascii")
        (jdk-path (getenv "JAVA_HOME")))
    (if jdk-path
        (expand-file-name command (expand-file-name "bin" jdk-path))
      command))
  "jdk native2ascii command path."
  :type 'string
  :group 'jprop)

(defvar jprop-mode-map
  (let ((m (make-sparse-keymap)))
    (define-key m "\C-cc" 'jprop-convert-buffer-to-buffer)
    (define-key m "\C-cr" 'jprop-convert-region-to-buffer)
    m))


(defun jprop-convert-region-to-1 (rev start end buff)
  (let ((command (concat jprop-native2ascii-path (if rev "" " -reverse"))))
    (save-excursion
      (shell-command-on-region start end command buff nil "*jprop-error*" t))))

(defun jprop-convert-region-to (rev start end buff)
  (let ((coding-system-for-read 'japanese-shift-jis-dos)
        (coding-system-for-write 'japanese-shift-jis-unix))
    (jprop-convert-region-to-1 rev start end buff)))

(defun jprop-convert-region-to-buffer (rev start end)
  (interactive "P\nr")
  (jprop-convert-region-to rev start end (get-buffer-create "*jprop*")))
(defun jprop-convert-buffer-to-buffer (rev)
  (interactive "P")
  (jprop-convert-region-to-buffer rev (point-min) (point-max)))

(defun jprop-convert-region (rev start end)
  (interactive "P\nr")
  (jprop-convert-region-to rev start end t))
(defun jprop-convert-buffer (rev)
  (interactive "P")
  (jprop-convert-region rev (point-min) (point-max)))

(add-hook 'conf-javaprop-mode-hook
          (lambda ()
            (let ((m (copy-keymap jprop-mode-map)))
              (set-keymap-parent m conf-javaprop-mode-map)
              (use-local-map m))))

(provide 'jprop)
2011/04/06 / highmt

オブジェクトパッセージ図 Ver.0.1.0

処理がどう進んでいくのかを分析するのに、コールグラフやコミュニケーション
図、シーケンス図、といったものをよく使うと思います。

しかし、これらの図は、基本的には呼び出しの関係を表すものであるため、特に
オブジェクトをつくってわたして、といったことを行うような処理では、オブジェ
クト間の関係を表すのにはがゆい思いをしたりします。

そこで、試験的にオブジェクトパッセージ図なるものを書いてみました。
以下のようなことがらを表すのを目標にしています:

  • オブジェクトaからオブジェクトbのm1を呼び出す
  • オブジェクトaからオブジェクトbのm1を呼び出してオブジェクトcを渡す
  • オブジェクトaからオブジェクトbのm1を呼び出してオブジェクトcを生成する

試みとして、Android の Service の使用方法をオブジェクトパッセージ図にし
てみました。
(この辺の内容のつもりですが間違ってるかもしれません。→http://developer.android.com/guide/developing/tools/aidl.html)
あまりよくできてないところもあるので、とりあえずVer.0.1.0として公開してみ
ます。

図の要素の説明等は今は雰囲気だけでいずれまた整理したいと思います。

オブジェクトパッセージ図

ソース(graphviz)

digraph sampleservice {
  graph [nodesep="1.2", ordering="in"];
  node [fontname="tahoma", fontsize="8"];
  edge [fontname="tahoma", fontsize="8", arrowsize="0.5"];

  AIDL [label="<<aidl>>\nIXxxService"];
  AIDL -> IXxxService_Stub [label="<<generate>>"];
  AIDL -> IXxxService [label="<<generate>>"];

  Binder;
  IXxxService_Stub [shape=Mrecord,label="<x>IXxxService.Stub|{<m1>doXxx|<m2>asInterface}"];
  Binder -> IXxxService_Stub [dir=back, arrowtail=empty, arrowsize="1.5"];

  XxxService_Stub [shape=Mrecord,label="<x>\<\<impl\>\>\nXxxService.Stub|{<m1>\<\<override\>\>\ndoXxx}"];
  IXxxService_Stub -> XxxService_Stub [dir=back, arrowtail=empty, arrowsize="1.5"];

  Service;
  XxxService [shape=Mrecord,label="<x>XxxService|{<m1>\<\<\override\>\>\nonBind}"];
  Service -> XxxService [dir=back, arrowtail=empty, arrowsize="1.5"];

  XxxServiceProxy [shape=Mrecord,label="<x>XxxServiceProxy(internal)|{<m1>\<\<\override\>\>\ndoXxx}"];
  IXxxService_Stub:m2 -> XxxServiceProxy [label="(8)\n<<lead to>>", style="dashed"];
  XxxService:m1 -> XxxService_Stub:x [label="(5)\n<<lead to>>", style="dashed"];

  Activity;
  XxxActivity [shape=Mrecord, label="<x>XxxActivity|{<c0>ctor()|<m1>\<\<\override\>\>\nonCreate|<m2>\<\<\override\>\>\nonDestroy|<m5>onKey|<m3>\<\<inherit\>\>\nbindService|<m4>\<\<inherit\>\>\nunbindService}"];
  Activity -> XxxActivity [dir=back, arrowtail=empty, arrowsize="1.5"];

  ServiceConnection;
  XxxServiceConnection [shape=Mrecord, label="<x>XxxServiceConnection|{<m1>\<\<override\>\>\nonServiceConnected|<m2>\<\<override\>\>\nonServiceDisconnected}"];
  ServiceConnection -> XxxServiceConnection [dir=back, arrowtail=empty, arrowsize="1.5"];
  
  XxxActivity:c0 -> XxxServiceConnection [label="(1)\n<<create>>"];
  
  Intent1 [label="Intent\n{ISampleService.class}"];
  XxxActivity:m1 -> Intent1 [label="(2)\n<<create>>"];
  XxxActivity:m1 -> XxxActivity:m3  [taillabel="(3)", labeldistance="6" labelangle="-20"];
  XxxActivity:m3 -> Android [label="(4)"];
  Intent1 -> XxxActivity:m3 [label="(3)\n<<passed to>>", style=dotted];
  XxxServiceConnection -> XxxActivity:m3 [label="(3)\n<<passed to>>", style=dotted];

  XxxActivity:m5 -> XxxServiceProxy:m1 [label="(10)"];
  
  XxxServiceConnection -> XxxActivity:m4 [label="(11)\n<<passed to>>", style=dotted];

  Android -> XxxService:m1 [label="(5)"];
  
  Android -> XxxServiceConnection:m1 [label="(7)"];
  XxxService_Stub -> XxxServiceConnection:m1 [label="(7)\n<<passed to>>", style=dotted];
  
  XxxServiceConnection:m1 -> IXxxService_Stub:m2 [label="(8)"];
  
  XxxActivity:m2 -> XxxActivity:m4 [taillabel="(11)", labeldistance="6", labelangle="-20"];
  XxxActivity:m4 -> Android [label="(12)"];
  Android -> XxxServiceConnection:m2 [label="(13)"];
}
2011/03/17 / highmt

javaのプロパティファイルを比較するWinMergeプラグイン

svnでどういう変更が入ったのか追いかけるのに不便だったのでつくってみた。
どこかにありそうなものだけど…

 
準備:

  1. JDKをインストールする
  2. 環境変数 JAVA_HOME をJDKのインストールディレクトリに設定(オプショナル)

 
設定:

  1. 以下を jprop.sct という名前で保存する:
    
    <scriptlet>
        <implements type="Automation" id="dispatcher">
            <property name="PluginEvent"><get/></property>
            <property name="PluginDescription"><get/></property>
            <property name="PluginFileFilters"><get/></property>
            <property name="PluginIsAutomatic"><get/></property>
            <method   name="UnpackFile"/>
            <method   name="PackFile"/>
        </implements>
        <script language="VBS">
    
    Option Explicit
    
    Const defaultJavaHome = "C:\jdk" '←ここは自分の環境にに応じて変更
    
    Function get_PluginEvent()
        get_PluginEvent = "FILE_PACK_UNPACK"
    End Function
    
    Function get_PluginDescription()
        get_PluginDescription = "Convert Java Propertis Files"
    End Function
    
    Function get_PluginFileFilters()
        get_PluginFileFilters = "\.properties$"
    End Function
    
    Function get_PluginIsAutomatic()
        get_PluginIsAutomatic = True
    End Function
    
    Function UnpackFile(fileSrc, fileDst, pbChanged, pSubcode)
        Dim WshShell
        Dim javaHome
        Dim objExec
    
        Set WshShell = CreateObject("WScript.Shell")
        javaHome = WshShell.Environment("Process")("JAVA_HOME")
        If javaHome = "" Then
            javaHome = defaultJavaHome
        End If
        Set objExec = WshShell.Exec(javaHome + "\bin\native2ascii -reverse """ + fileSrc + """ """ + fileDst + """" )
        Do While objExec.Status = 0
            'WScript.Sleep(100)
        Loop
    
        pbChanged = True
        pSubcode = 0
    
        UnpackFile = True
    End Function
    
    Function PackFile(fileSrc, fileDst, pbChanged, pSubcode)
        Dim WshShell
        Dim javaHome
        Dim objExec
    
        Set WshShell = CreateObject("WScript.Shell")
        javaHome = WshShell.Environment("Process")("JAVA_HOME")
        If javaHome = "" Then
            javaHome = defaultJavaHome
        End If
        Set objExec = WshShell.Exec(javaHome + "\bin\native2ascii """ + fileSrc + """ """ + fileDst + """" )
        Do While objExec.Status = 0
            'WScript.Sleep(100)
        Loop
    
        pbChanged = True
        pSubcode = 0
    
        PackFile = True
    End Function
    
        </script>
    </scriptlet>
    
    
  2. jprop.sctを WinMerge をインストールしたディレクトリの下の
    MergePlugins ディレクトリに置く

 

使い方:

  1. WinMergeで比較する
  2. [プラグイン]-[展開プラグインで開く]を選び、
    [展開プラグインの選択]ダイアログの[ファイル展開プラグイン]で
    jprop.sctを選ぶ

 

WScriptオブジェクトにアクセスできないので
ポーリング中Sleepしてないのが難点ですが、
最近のマルチコアなPCならさほど影響はないでしょうきっと…
もしくは、ExecじゃなくてRunとか使えばいいのかもしれませんが…