本稿は JW_CAD 外部変形のプログラムを作成されている方の読み物として掲載しています。 ( 改定 )
 JW_CAD 外部変形 の 良さは 数多くの プログラム言語が
利用できることだと思います。
 awk や ruby は とくに 利用されています。LISP も使え
るはずなのですが なかなか 公開されません。
 LISP の 外部変形が AutoLISP と 互換性でもあれば事情
は 違ってくるのでしょうが? なかなか
 ともあれ 本稿では clisp( ANSI Common Lisp )によって
LISP の 外部変形の実態にせまってみようと思います。

 LISP の 外部変形は こんな風にできます。
:/□○を一度に使う
@echo off
REM #jw
REM #1- 始点を指示してください
REM #2 終点を指示してください
REM #e
set src=\jww\Lite\pro\jw
echo ^
(外変^
  (○(□(/ 1 2)))^
) | clisp -q -i %src% > nul
 あるいは
:/□○を一度に使う
@echo off
REM #jw
REM #1- 始点を指示してください
REM #2 終点を指示してください
REM #e
@set src=\jww\Lite\pro\jw
@findstr /v "^@ ^REM ^:" %0 | clisp -q -i %src% > nul
@exit
(外変
  (○(□(/ 1 2)))
)
 → 変数や関数名に日本語が使えます。  → %src% は 外部変形用ライブラリ jw.lisp です。


| 前へ | 表紙へ |

  • ♪ 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 とほぼ同じで
    す。

     健康第一!! またお会いいたしましょう。

    | 前へ | 表紙へ |