♪ Lite & Seen Lite は みんなの道具。
はじめに
LISP 自体は awk や ruby は おろか C や BASIC よりも
古い言語で、Common Lisp は LISP の 方言のひとつだそう
です。
clisp は ANSI Common Lisp に基づくフリーソフトで 日
本語も使え Windows でも動きます。
実用性の観点から見れば
AutoLISP が Common Lisp を ベースにしているというこ
となので clispによる外部変形も成熟してくれば AutoLISP
と 部分的ではあるにせよ互換性のあるプログラムが作れる
ようになると思われます。
clisp は
下記のサイトから入手できます。
http://sourceforge.net/project/showfiles.php?group_id=1355
下記のサイトにわかりやすい説明があります。
Common Lisp Programming / M.Hiroi's Home Page
http://www.geocities.jp/m_hiroi/clisp/index.html
Common Lisp のコマンドリファレンスのサイトです。
Common Lisp HyperSpec
http://www.lispworks.com/documentation/HyperSpec/Front/index.htm
clisp のスクリプトの性能をチェックします。
コマンドプロンプトで 円周率 π を表示させてみます。
C:\>clisp -q -x "pi"
3.1415926535897932385L0
1行プログラムです。うまくゆきました。
パイプを利用してみます。
C:\>echo pi | clisp -q
[1]>
3.1415926535897932385L0
これも大丈夫です。パス や タイム などは
echo (princ "%path%") | clisp -q -
echo (princ "%time%") | clisp -q -
echo (princ "%date%") | clisp -q -
echo (princ "%cd%") | clisp -q -
echo (princ "%comspec%") | clisp -q -
のようにすれば 表示できます。
プログラムファイルを実行してみます。
プログラムファイル [000pi.lisp]
(princ pi)
を コマンドプロンプトで実行します。
C:\jww\Lite\bat\script\clisp>clisp -q 000pi.lisp
3.1415926535897932385L0
問題ありません。
→ 参考までに
000pi.lisp を ライブラリとして 扱うことも
C:\jww\Lite\bat\script\clisp>clisp -q -x "(require :000pi.lisp)"
;; Loading file 000PI.LISP ...
3.1415926535897932385L0
;; Loaded file 000PI.LISP
T
できるようです。
clisp は awk や ruby と同じような使い方ができるよう
です。
|先頭へ戻る|
ファイルの入出力
外部変形で必要な clisp の入出力コマンドです。
[ ファイルの入出力 ]
(setq ストリーム名 (open "ファイル名" ファイルモード))
〜
(close ストリーム名)
あるいは
(with-open-file (ストリーム名 "ファイル名" ファイルモード)
〜
)
○ファイルモード
入力 :direction :input [:if-does-not-exist nil]
出力 :direction :output
追加 :direction :output :if-exists :append
[ ファイルの有無 ]
(probe-file "ファイル名")
外部変形機能のサンプルバッチ JWW_SMPL.BAT を表示する
プログラムを考えてみます。
MS-DOS なら コマンドプロンプトで
C:\>cd c:\jww
C:\jww>type JWW_SMPL.BAT | more
とするだけですが、
clisp では
プログラムファイル [000type.lisp]
(setq f (open "JWW_SMPL.BAT"))
(loop for line = (read-line f nil) while line do
(write-line line)
)
(close f)
を 作っておいて それを コマンドプロンプトで
C:\jww>clisp -q 000type.lisp | more
として 実行するやり方が正攻法のようです。
同じことを 外部変形にするなら こうなります。
バッチファイル [000type.lisp.bat]
@rem JWW_SMPL.BAT を表示する (clisp)
@echo off
REM #jw
REM #cd
clisp -q 000type.lisp | more
pause
バッチファイルに 直接プログラムを書くこともできます。
バッチファイル [000type.lisp-2.bat]
@rem JWW_SMPL.BAT を表示する (clisp-2)
@echo off
REM #jw
echo ^
(with-open-file (f "JWW_SMPL.BAT")^
(loop for line = (read-line f nil) while line do^
(write-line line)^
)^
) | clisp -q | more > nul
pause
このやり方は MS-DOS の影響を直接受けるため % < > | &
などの特殊な記号は %% ^^^< ^^^> ^^^| ^^^& などと しな
ければなりません。
さて このやり方で 用紙の中央に 円周率 π を書いてみ
ることにします。これが大変でした。" の出力が うまくい
かないのです。それでも なんとかなりました。
@rem 用紙の中央に 円周率 π を書く
@echo off
REM #jw
REM #e
set dq=^\^"
echo ^
(with-open-file (f "jwc_temp.txt" :direction :output)^
(format f "ch 0 0 1 0%%dq%%~f~%%" pi)^
) | clisp -q > nul
スマートなやり方もあります。
more +n を利用すれば MS-DOS の影響は受けません。
@rem 用紙の中央に 円周率 π を書く
@echo off
REM #jw
REM #e
more +5 %~f0 | clisp -q > nul & goto:eof
(with-open-file (f "jwc_temp.txt" :direction :output)
(format f "ch 0 0 1 0 \"~f~%" pi)
)
findstr も 使えます。
findstr /v /i "echo rem" %~f0 | clisp -q > nul & goto:eof
(with-open-file (f "jwc_temp.txt" :direction :output)
(format f "ch 0 0 1 0 \"~f~%" pi)
)
"echo rem" がある行をスキップしているだけです。
|先頭へ戻る|
正規表現とデータの取得
外部変形で必要な clisp の正規表現のコマンドです。
[ マッチング ]
(regexp:match "正規表現パターン" 対象文字列)
○正規表現メタ文字
( は \\(
| は \\|
) は \\)
? は \\?
+ は \\+ とします
* . ^ $ [ ] は そのまま使えます。
○指示点データを取得する例
[ ruby ]
/^hp[1-9][0-9]?(ln|ci|ch)?-?/i =~ line
[ clisp ]
(regexp:match "^hp[1-9][0-9]\\?\\(ln\\|ci\\|ch\\)\\?-\\?" line
:ignore-case t)
[ 文字列をリストに分割する ]
(regexp:regexp-split "区切り文字列" 対象文字列)
区切り文字列は 正規表現パターン が利用できる
使用例
(regexp:regexp-split " \\+" "1 2 3 4 5")
→ ("1" "2" "3" "4" "5")
外部変形の
座標データは テキストファイル(jwc_temp.txt)に
hp1- -92.5423728813559 -13.3474576271186
hp2 22.6906779661017 75.635593220339
のように 有効桁数15で出力されます。
面倒ですが このデータを読み取るプログラムはユーザー
が書かなければなりません。
awk や ruby なら 正規表現を使います。
正規表現は Common Lisp のコマンドには見あたらないよ
うですが clisp では使えます。
座標データを読み込むプログラムを考えてみます。
(with-open-file (f "jwc_temp.txt" :direction :input)
(loop for line = (read-line f nil) while line do
(setq a (regexp:regexp-split " \\+" line))
(if (regexp:match "^hp1" line) (setq hp1 (cdr a)))
(if (regexp:match "^hp2" line) (setq hp2 (cdr a)))
)
)
座標データ hp1 と hp2 はつぎのようなイメージで取得
されます。
(setq hp1 (list "-92.5423728813559" "-13.3474576271186"))
(setq hp2 (list "22.6906779661017" "75.635593220339"))
LISP らしく言えば hp1 や hp2 は 座標リストです。
|先頭へ戻る|
文字列を数値に変換する
前項で取得した 座標値は 文字列です。
文字列を数値に変換する方法を考えなければなりません。
awk なら
"123"+1.0 は 124.0
となり、ruby なら
"123".to_f + 1 とすれば 124.0
となります。
awk や ruby では 文字列になった数値をもとに戻すこと
は簡単ですが clisp は どうでしょうか?
(+ (read-from-string "123") 1.0) で 124.0
となります。これで 何の問題もないように思えます。
念のために 前項で取得した 座標値で 試してみると
(+ (read-from-string "-92.5423728813559") 1.0) は -91.54237
さらに 調べると
[1]> (format t "~,15g"
(+ (read-from-string "-92.5423728813559") 1.0))
-91.5423700000000
となり 明らかに 問題があります。
いろいろ検討した結果、このようにすればなんとかなり
ます。
[2]> (format t "~,15g"
(+ (read-from-string "-92.5423728813559d0") 1.0d0))
-91.5423728813559
もっと簡単なやり方が あるとは思うのですが?
文字列を数値(double-float)に変換する関数 to_f を用意
して それを使えるようにしておけば
[3]> (format t "~,15g"
(+ (to_f "-92.5423728813559") 1.0d0))
-91.5423728813559
のような 処理ができるようになります。
追記
BASIC の LEFT$, RIGHT$, MID$ に相当するコマンドです。
→ 厳密ではありません。こんな感じだったと思います。
[ LEFT$(str,n) ]
(subseq str 0 n)
[ RIGHT$(str,n) ]
(subseq str (- (length str) n))
[ MID$(str,m,n) ]
(subseq str m (+ m n))
[ 文字列 str で "search" のある位置 ]
(search str "search") => "search" がなければ NIL を 返す
|先頭へ戻る|
距離と角度の計算
2点の距離とそれを結ぶ線の角度を計算してみます。
計算をはじめる前に 2点の座標データ
hp1- -92.5423728813559 -13.3474576271186
hp2 22.6906779661017 75.635593220339
を 2点の座標リスト
(setq hp1 (list "-92.5423728813559" "-13.3474576271186"))
(setq hp2 (list "22.6906779661017" "75.635593220339"))
として与えておきます。
座標リスト hp から X座標値 x Y座標値 y へ数値 を
代入してみます。
car と cadr を利用すればできるはずです。
(setq x1 (to_f (car hp1)))
(setq y1 (to_f (cadr hp1)))
あるいは
(setq x1 (to_f (car hp1)) y1 (to_f (cadr hp1)))
つぎのようにすれば 一度にできるようです。
(multiple-value-setq (x1 y1) (values-list (to_f hp1)))
どちらにしても awk や ruby から見れば 面倒くさい感
は否めませんが そんなに急いでどこへいく! 派の方には
clisp が いいかもしれません。
さて 2点 hp1 と hp2 の距離 c はピタゴラスの定理から
計算できます。
(multiple-value-setq (x1 y1 x2 y2)
(values-list (to_f (append hp1 hp2))))
(setq a (- x2 x1))
(setq b (- y2 y1))
(setq c (sqrt (+ (* a a) (* b b))))
a が 直角三角形の底辺で b は 高さに相当します。
a, b を 利用すれば
2点を結ぶ線の角度 d は 下記のように計算されます。
(setq d (atan b a))
余談ですが
(atan b a) は a と b が 0 のとき エラーとなります。
下記のような 関数 atan2 を 用意しておけば便利です。
(defun atan2 (b a) (if (and (= a 0) (= b 0)) 0 (atan b a)))
a と b が 0 のとき 0 を返します。
|先頭へ戻る|
ユーザー定義関数とマクロ
関数は defun マクロは defmacro コマンドです。
[ ユーザー定義関数 ]
(defun 関数名 (引数・・・) 式)
○return
(return-from 関数名 [戻り値])
[ マクロ ]
(defmacro マクロ名 (引数・・・) 式)
2点の距離を返す関数 distance を 定義してみます。
(defun distance (hp1 hp2)
(multiple-value-setq (x1 y1 x2 y2)
(values-list (to_f (append hp1 hp2))))
(setq a (- x2 x1))
(setq b (- y2 y1))
(setq c (sqrt (+ (* a a) (* b b))))
)
前項のプログラムをそのまま引用しています。
使ってみます。
(distance (list 0 0) (list 3 4))
(princ c) => 5.0d0
関数内で定義した変数 c が使えるようです。
c だけでなく a b も そのようです。awk と 同じような
感じです。つくりなおしてみます。
(defun distance (hp1 hp2)
(let (a b x1 y1 x2 y2)
(multiple-value-setq (x1 y1 x2 y2)
(values-list (to_f (append hp1 hp2))))
(setq a (- x2 x1))
(setq b (- y2 y1))
(sqrt (+ (* a a) (* b b)))
))
c は 値を返すだけなので とりやめ a と b は 関数の外
で使えなくしています。
2点を結ぶ線の角度を返す関数 angle は distance をす
こし手直しすれば できあがります。
(defun angle (hp1 hp2)
(let (a b x1 y1 x2 y2)
(multiple-value-setq (x1 y1 x2 y2)
(values-list (to_f (append hp1 hp2))))
(setq a (- x2 x1))
(setq b (- y2 y1))
(atan b a)
))
さて、
マクロです。わかっているようでわかりません。
試しに 距離を 寸 に 角度を 寸勾配 に変換するマクロ
を考えてみます。
(defmacro 寸 (x &rest y)
(if (equal 'distance x)
(/ (eval `(,x ,@y)) 30.3)
(* (tan (eval `(,x ,@y))) 10))
)
このようにして
(寸 distance hp1 hp2)
(寸 angle hp1 hp2)
ふぅ〜ん・・・ これが LISP ですか。
|先頭へ戻る|
コマンドライン引数と環境変数の取得
コマンドライン引数は *args* に セットされます。
@echo off
echo ^
(loop for x in *args*^
sum (parse-integer x)^
) | clisp -q -- 1 2 3 4 5 6 7 8 9 10
pause
=> 55
環境変数 path を確認してみます。
@echo off
echo (ext:getenv "path") | clisp -q
pause
exit
=> " 〜 ;C:\\Program Files\\clisp-2.48;"
|先頭へ戻る|
簡単な外部変形
最後に簡単な外部変形をいくつか紹介します。
電卓を開く
@rem 電卓を開く
@echo off
REM #jw
REM #e
clisp -q -x "(ext:run-shell-command \"start calc\")" > nul
del jwc_temp.txt
実行しているディレクトリをコメント表示する
@rem 実行しているディレクトリ
@echo off
REM #jw
REM #e
echo ^
(with-open-file (f "jwc_temp.txt" :direction :output)^
(format f "h#cd = ~a~%%" (ext:cd))^
) | clisp -q > nul
時刻を書き込む : どこかのプログラムを引用
@rem 時刻を書き込む
@echo off
REM #jw
REM #0 時刻を書き込む基点を指示してください。
REM #e
more +8 %~f0 | clisp -q > nul
goto:eof
(setq jweek '("月" "火" "水" "木" "金" "土" "日"))
(setq utime (get-universal-time))
(setq jtime
(multiple-value-bind (s m h dd mm yy ww)
(decode-universal-time utime)
(format nil
"日本時間 ~4,'0d/~2,'0d/~2,'0d ~2,'0d:~2,'0d:~2,'0d(~a)"
yy mm dd h m s (nth ww jweek))
)
)
(with-open-file (jw "jwc_temp.txt" :direction :output)
(format jw "ch 0 0 1 0 \"~a~%" jtime)
)
指示した文字列を逆書きする
→ 特殊記号などには対応していません。
@rem 文字列を逆書きする
@echo off
REM #jw
REM #1ch 文字を指示してください。
REM #e
more +8 %~f0 | clisp -q > nul
goto:eof
(with-open-file (f "jwc_temp.txt" :direction :input)
(with-open-file (g "jwc_temp.bak" :direction :output)
(loop for line = (read-line f nil) while line do
(setq a (regexp:regexp-split " \\+" line))
(print a)
(if (regexp:match "^ch" line)
(progn
(setq s (search "\"" line))
(setq str (subseq line (+ s 1) (length line)))
(write-line "hd" g)
(format g "~{~a ~}\"~a~%"
(subseq a 0 5) (reverse str))
)
(if (regexp:match "^\\(lg\\|ly\\|cn\\)" line)
(write-line line g))
)
)
)
)
(delete-file "jwc_temp.txt")
(rename-file "jwc_temp.bak" "jwc_temp.txt")
2点を結ぶ線を引く
@rem 2点を結ぶ線
@echo off
REM #jw
REM #1- 始点を指示してください : (setq hp1 (getpoint))
REM #2 終点を指示してください : (setq hp2 (getpoint))
REM #e
more +8 %~f0 | clisp -q > nul
goto:eof
(with-open-file (f "jwc_temp.txt" :direction :input)
(loop for line = (read-line f nil) while line do
(setq a (regexp:regexp-split " \\+" line))
(if (regexp:match "^hp1" line) (setq hp1 (cdr a)))
(if (regexp:match "^hp2" line) (setq hp2 (cdr a)))
)
)
(with-open-file (f "jwc_temp.txt" :direction :output)
(format f "~{ ~a~}~%" (append hp1 hp2))
)
作図用の関数を作っておけば プログラムはつぎに示すく
らいまで 単純になります。
echo (jw (line 1 2)) | clisp -q -i %~dp0jw > nul
あるいは
clisp -q -i %~dp0jw -x "(jw (line 1 2))" > nul
外部変形では 指示データに番号がつけられます。これは
とても便利な機能です。
線は (line x1 y1 x2 y2) か (line (x1 y1)(x2 y2)) と
して 引くのでしょうが 上記の例は 座標リストのかわりに
指示点の番号を与えています。このように単純に 表現でき
るところが 外部変形の長所です。短所はプログラムを作る
ために 10年以上 かかったことです。
参考までに H形鋼断面の作図は
echo (jw (jisH 400 200 8 13 13)) | clisp -q -i %~dp0jw > nul
のようにすれば 外部変形ができるようになります。
→ 参考資料 H形鋼断面を描く外部変形
このプログラムは 単独で実行できます。
コマンドラインオプションは clisp -h で 確認してくだ
さい。
○書式設定
http://super.para.media.kyoto-u.ac.jp/~tasuku/format-func.html
(format nil "pt ~a ~a" 1 2)) → pt 1 2
○三項演算子
条件 ? 真の場合 : 偽の場合 は
(if (条件)
(真の場合)
(偽の場合)
)
あるいは
(cond
((条件) (真の場合))
(t (偽の場合))
)
となります。
|先頭へ戻る|
あとがき
職人が自分にあった最高の道具をつかうように 外部変形
も いまさら awk や ruby ではなく 自分にあったツールを
選ぶ あるいは 選べる時代がきていることは 間違いありま
せん。
それにしても
LISP はとても扱いにくく難解です。それは LISP 独自の
文法にあるのだと思います。VB や C++ などと比べても プ
ログラムを作るのは 決して簡単ではありません。それでも
LISP は LISP です。パソコンの性能が やっと LISP に 追
いついてきたのでしょう。
思えば 管理人は AutoLISP でつまづきました。それから
20年・・・
それでも なんとか
外部変形 で LISP を つかまえました。
外部変形と 最も相性のよいプログラム言語は言うまでも
なく awk です。つぎは ruby か VB だと思います。そして
LISP は 外部変形には向いていません。ただし 外部変形で
はなく マクロのような機能を JW_CAD に作りたいのであれ
ば LISP が 最適だと思います。できるかどうかは別として
の話ですが 可能性が最もありそうなのは LISP です。
LISP は ( 〜 ) のパーツを積み木のように組み合わせる
ことがプログラムなので プログラムを寄せ集めて ひとつ
のプログラムを作ることも それほど 難しい作業とは なら
ないでしょう。要は いいとこどりをしていれば 自然に 必
要なプログラムが出来上がってくるという 具合です。
LISP を 関数型のプログラムという人がいます。 確かに
(sin d) (sqrt 2) のほか 足し算ですら (+ 1 2) のように
処理されます。
外部変形は 単純にその延長で (line 1 2) として線を引
くことを考えればいいわけです。それでも プログラムは難
解です。ただ 足し算をするように 線が引けるのは とても
魅力的です。1+2 で 構わないと思うのか (+ 1 2) も 受け
入れるのか。それが問題です。
LISP してますか? やっとできました。
試作段階ですが
clisp 作図ライブラリ と 例題集( LISP で 外部変形 )
を公開しています。
clisp による 外部変形の処理速度は ruby とほぼ同じで
す。
健康第一!! またお会いいたしましょう。
| 前へ | 表紙へ |