-
Notifications
You must be signed in to change notification settings - Fork 6
qiita introduction
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 ()
{
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 に見えている変数なら、そのまま参照することが出来ます。
(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
ループ, 代入演算子 (\=
と +\=
), 二項演算 (<
), インクリメント
も使えます。
;; 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 の括
弧として解釈されます。
(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 しちゃうのもそのまま再現!!
(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
はとても重要な制御構文です。
(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