Skip to content

qiita introduction

y2q-actionman edited this page Aug 19, 2014 · 1 revision

ストーリー

Lisp は、「括弧が多く」「前置記法」であることにより、利用者が伸び悩んで いると言われている。Lisper 共にとっては、記法など見栄えの問題に過ぎず、 これを省みる者はいない。とはいえ、中置記法に染められた人々が一定数いる ということもまた事実であろう。

これは、彼らをこちらにいざなうために、中置記法を取り入れてみる一つの実 験である。

真のストーリー: 私の体験

あるデータ構造を、 Common Lisp で実装しようと思い立ちました。そのデータ 構造には、既に C 言語での実装があったので、「まあ元々の論文を読むのは面 倒だし、とりあえずアルゴリズムをコピペするか」と思っていました。

そこで私は思ったのです: Common Lisp の配列参照は面倒すぎる!

例えば、以下の C の式を考えます:

x[i] = y[ z[i + 1] + 1 ] - 1;

これが、 Common Lisp だと:

(setf (aref x i)
      (1- (aref y (1+ (aref z (1+ i))))))

Common Lisp 版は、なんだかよく分からない気がします。配列参照についてく らい、DSL的な感じで C 言語の構文を取り入れられないかしら、と思ったので す。

というわけで何かいろいろやっていたら・・配列参照以外も、結構な C の構文 が食えるようになってしまいました。

書いたもの: with-c-syntax マクロ

Hello, World!

(with-c-syntax ()
{
   print \( "Hello, World!" \) \;
})

with-c-syntax とつけて括弧で囲めば、その中では C 言語的な構文が使い放 題。そんなマクロです。 ( ) ; という文字は、 Lisp では特殊な意味があるのでエスケープを付けて います。

値を返してみる

(defun test-add-args (x y)
  (with-c-syntax ()
    {
    return x + y \;
    })
  )

(test-add-args 1 2) ; => 3

lexical に見えている変数なら、そのまま参照することが出来ます。

for ループで、 1 から 100 まで足そう

(defun test-for-loop ()
  (let ((i 0) (sum 0))
    (with-c-syntax ()
      {
      for \( i = 0 \; i < 100 \; ++ i \)
         sum += i \;
      })
    sum))

(test-for-loop) ; => 5050

for ループ, 代入演算子 (\=+\=), 二項演算 (<), インクリメント も使えます。

ループを break したり continue したり Lisp 式を混ぜたり

;; 50 未満の偶数の和を取る
(defun test-loop-continue-break ()
  (with-c-syntax ((i 0) (sum 0))
   {
    for \( i = 0 \; i < 100 \; ++ i \) {
      if \( (oddp i) \) ; Lisp 関数 oddp
        continue \;
      if \( i == 50 \)
        break \;
      sum += i \;
      (format t "i ~A, sum ~A~%" i sum) \; ; Lisp 関数 format
    }
   return sum \;
   }))

(test-loop-continue-break) ; => 600

C 系言語でお馴染みの continue, break は、みなさんご存知の挙動をしま す。 Lisp 式を混ぜ込むことも可能です。括弧をエスケープしなければ、 Lisp の括 弧として解釈されます。

switch-case しよう

(defun test-switch ()
  (flet ((fun (x)
           (with-c-syntax ((x x))
    {
      format \( t \, "[~A] " \, x \) \;
      switch \( x \) {
      case 1 \:
        (format t "case 1~%") \;
        break \;
      case 2 \:
        (format t "case 2~%") \;
        (format t "fall-though 2->3~%") \;
      case 3 \:
        (format t "case 3~%") \;
        break \;
      case 4 \:
        (format t "case 4~%") \;
        break \;
      default \:
        (format t "default~%") \;
      }
    })))
    (loop for i from 0 to 5
       do (fun i))))

(test-switch)
;; 以下のように印字される
#|
[0] default
[1] case 1
[2] case 2
fall-though 2->3
case 3
[3] case 3
[4] case 4
[5] default
|#

switch - case もそのままに。 switch 文で break を忘れると fall through しちゃうのもそのまま再現!!

goto 無双

(defun test-goto ()
  (with-c-syntax ()
    {
      goto d \;
    a \:
      princ \( "a" \) \;
    b \:
      princ \( "b" \) \;
      goto e \;
    c \:
      princ \( "c" \) \;
      return \;
    d \:
      princ \( "d" \) \;
      goto a \;  
    e \:
      princ \( "e" \) \;
      goto c \;
    })
  )

(test-goto)
;; 以下のように印字される
#|
dabec
|#

Common Lisp においても、 goto はとても重要な制御構文です。

Duff’s Device

(defun test-duff-device (to-seq from-seq cnt)
  (with-c-syntax ((to-seq to-seq) (from-seq from-seq) (cnt cnt)
                  to from n)
    {
    to = & to-seq \;          ; produces a pointer
    from = & from-seq \;      ; (same as above)

    n = \( cnt + 7 \) / 8 \;
    n = floor \( n \) \;                ; CL:/ produces rational. cast it.
    switch \( cnt % 8 \) {
    case 0 \:	do {	* to ++ = * from ++ \;
    case 7 \:		* to ++ = * from ++ \;
    case 6 \:		* to ++ = * from ++ \;
    case 5 \:		* to ++ = * from ++ \;
    case 4 \:		* to ++ = * from ++ \;
    case 3 \:		* to ++ = * from ++ \;
    case 2 \:		* to ++ = * from ++ \;
    case 1 \:		* to ++ = * from ++ \;
      } while \( -- n > 0 \) \;
    }
    })
  to-seq)

#| 
実行例

CL-USER> (setf arr1 (make-array 20 :initial-element 1))
#(1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1)
CL-USER> (setf arr2 (make-array 20 :initial-element 2))
#(2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2)
CL-USER> (test-duff-device arr1 arr2 10)
#(2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1)
CL-USER> arr1
#(2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1)
|#

こんな とんでもなく曲がりくねった コードだって使えてしまいます。

現在の実装の進捗

式 (Expression) と、文 (statement) は、ほぼ何でも処理できます。未対応な のは、 sizeof と, キャスト演算だけです。

一方、宣言 (declaration) は一切使えません。そのため、 with-c-syntax 内部で変数を作れないという重大な制限があります。これは直します。

また、 Lisp リーダが独自の解釈をする文字にはエスケープが必要だったり、 くっついてしまう文字には空白を入れて離す必要があったりします。これは適 切なリーダーマクロを用意することによって解決できる予定です。

コード

コードはこちら

https://github.com/y2q-actionman/with-c-syntax.git