2010年2月9日火曜日

newLISP基礎文法最速マスター

待っていても誰も書きそうにないので自分で作りました。

これは何?

Lisp風スクリプト言語newLISPの文法一覧です。(→newLISP公式ページ)
S式について多少でも知っている人は他の処理系と比較してもよし。
そもそもS式を知らない人はLisp入門にしてもよし。 (先に他の有名なScheme処理系やCommon Lispに手を出す方が無難ですが)
newLISPの簡易リファレンスとして利用できるかと思います。ただし無保証です。

基礎

対話環境として使う

$ newlisp -C
newLISP v.10.1.7 on Win32 IPv4 UTF-8, execute 'newlisp -h' for more info.

> _

スクリプトを実行する

$ newlisp file.lsp

評価

コマンドラインからの利用では、複数行にわたるS式の評価は[cmd]~[/cmd]タグを用いる必要があります。
> [cmd]
(define (fact n)
  (if (= n 0)
      1
    (mul n (fact (- n 1)))))
[/cmd]
> (fact 30)
2.652528598e+032
> _

表示

;; Hello World を表示する
(println "Hello World")

コメント

; コメントその1
# コメントその2
複数行コメントは使えません。
特に使い分ける必要もないので、他のLISP処理系に倣って セミコロン(;)で統一することを推奨します。
ファイル先頭行の#!/usr/bin/newlispくらいしか使い道がないんじゃなかろうか。

変数の宣言と代入

変数を宣言する必要はありません。好きなときにシンボルを参照し、値を代入できます。
存在しないシンボルを参照する場合はシンボル生成時にnilが代入されます。
(set 'a 10)    ;=> 10
(setq b 20)    ;=> 20
c              ;=> nil
とはいえset/setqの濫用はグローバル変数の多用と大差ないので、 let/letn/localやlambdaで変数を束縛してから代入を行うことを推奨します。
(let ((a 10)
      (b 20))
  ...)
(local (a b)
  (setq a 10 b 20)
  ...)

データ型

ブール (bool)
nil, true
整数 (integer)
10, 0655, 0xff
浮動小数 (float)
3.141592, (mul 4 (atan 1)), 10e-3
文字列 (string)
"string", "\x73\x74\x72\x69\x6e\x67", {文字列}
シンボル (symbol)
a, Symbol, XYZ
コンテキスト (context)
MAIN, Class, Tree
関数 (primitive, lambda, macro, cdecl, stdcall)
define, apply, cons, (lambda (x) (* x x)), (lambda-macro () (set (args 0) (eval (args 1)))), (import "libc.so.6" "printf")
リスト (list)
(1 2 3 4), ((a 1 2 3) (b 10 20 30))
配列 (array)
(array 2 3)

数値

整数と浮動小数が扱えます。複素数は扱えません。
123                             ;=> 123
1.234                           ;=> 1.234
1.23e+4                         ;=> 1.23e+4 (科学的表記)
(sqrt -1)                       ;=> nan (Not a Number)
(div 1 0)                       ;=> inf (無限大)
;; 8進数
0744                            ;=> 484
;; 16進数
0xDEADBEEF                      ;=> 3735928559

四則演算

通常の四則演算(+-*/)は整数のみを扱います。 引数が小数の場合は切り捨てられます。
(+ 1 2)                         ;=> 3
(- 1 2)                         ;=> -1
(* 1 2)                         ;=> 2
(/ 1 2)                         ;=> 0 (※1/2でない)
;; 余り
(% 3 2)                         ;=> 1
;; べき乗
(pow 2 10)                      ;=> 1024 (2^10)
浮動小数を扱う場合は次の関数を使用します。
;; add(+) sub(-) mul(*) div(/) mod(%)
(add 1 2)                       ;=> 3
(sub 1 2)                       ;=> -1
(mul 1 2)                       ;=> 2
(div 1 2)                       ;=> 0.5
;; 関数を上書きしてが浮動小数が扱えるように
(constant '+ add '- sub '* mul '/ div)

(+ 10.2 0.3)                    ;=> 10.5
(- (sin 1))                     ;=> -0.8414709848

インクリメントとデクリメント

(inc a)                         ; == (setq a (+ a 1))
(inc a 10)                      ; == (setq a (+ a 10))
(dec a)                         ; == (setq a (- a 1))

文字列

文字列の表現

文字列は次のいずれかの要素で囲って表します。
  • ダブルクォート ""
  • ブレース {}
  • タグ [text]~[/text]
Perl,Rubyのような変数展開の機能はありません。
ダブルクォート文字列の中では"\n"(改行),"\t"(タブ)などの特殊文字が使えます。
(print "This\tis a \"string\"\n")
;-> This    is a "string"
{}と[text]~[/text]表記ではバックスラッシュもリテラルとして扱われます。
{This is a "string"}
[text]This is a "string"[text]
また、現在のnewLISPではunicode(UTF-8)を扱えるバージョンと そうでないバージョンがあり、前者ならば日本語を含む文字列も処理できます。

文字列操作

;; 結合
(append "foo" "bar" "baz")      ;=> "foobarbaz"

(join '("foo" "bar" "baz"))     ;=> "foobarbaz"
(join '("foo" "bar" "baz") ",") ;=> "foo,bar,baz"
;; 分割
(parse "This is it")            ;=> ("This" "is" "it")
(parse "aaa,bbb,cc" ",")        ;=> ("aaa" "bbb" "cc")
;; 長さ
(length "The quick brown fox jumps over the lazy dog") ;=> 43
(length "日本語文字列")         ;=> 18 (バイト数)
(utf8len "日本語文字列")        ;=> 6 (文字数)
;; 切り出し
(slice "newLISP" 0 3)           ;=> "new"
(slice "newLISP" 0 -1)          ;=> "newLIS"
;; 検索
(find "fox" "The quick brown fox jumps over the lazy dog")
;=>; 16 (開始位置)
;; 置換
(setq str "Newlisp")
(setf (first str) (lower-case $it))
str                                     ;=> "newlisp"
(replace "lisp" str (title-case $it) 0) ;=> "newLisp"

リスト

リストは括弧()で要素を囲んで表します。 newLISPはCLやSchemeとちょっと違っていて「ドット対」が存在しません。
(list 'a 'b)                    ;=> (a b)
(cons 'a 'b)                    ;=> (a b)
(= (list 1 2) (cons 1 2))       ;=> true

要素の参照と代入

(setq lst '(zero one 2 "3" "four" FIVE))
(lst 0)                         ;=> zero
(lst -1)                        ;=> FIVE
(nth 4 lst)                     ;=> "four"
;; 最初の要素
(first lst)                     ;=> "foo"
;; それ以外の要素
(rest lst)                      ;=> (one 2 "3" "four" FIVE)
;; 最後の要素
(last lst)                      ;=> FIVE
;; 代入にはCommon Lispのようなsetfマクロが利用できる
(setf (lst 0) "foo")            ;=> "foo"
(setf (lst 2) (* $it 2))        ;=> 4
lst                             ;=> ("foo" one 4 "3" "four" FIVE)
;; 要素数
(length lst)                    ;=> 6
;; リスト操作
(setq lst '(1 2 3))
;; 先頭の要素を取り出す
(pop lst)                       ;=> 1
lst                             ;=> (2 3)
;; 先頭に要素を追加する
(push 10 lst)
lst                             ;=> (10 2 3)
;; 末尾の要素を取り出す
(pop lst -1)                    ;=> 3
lst                             ;=> (10 2)
;; 末尾に要素を追加する
(push 999 lst -1)
lst                             ;=> (10 2 999)

ハッシュ

newLISPの基本要素にハッシュはありませんが コンテキストの機能で代用できるようになっています。
キーには文字列を、値には任意のオブジェクトを指定できます。
;; ハッシュの作成
(new 'Tree 'Hash)
;; 要素の追加
(Hash '(("a" 1) ("b" 2)))       ; (まとめて)
(Hash "d" 10)                   ; (別個に)
;; 要素の参照
(Hash "a")                      ;=> 1
(Hash "c")                      ;=> nil (見つからなかった)
(Hash)                          ;=> (("a" 1) ("b" 2) ("d" 10))
;; 要素の代入
(Hash "c" "Comp")               ;=> "Comp"
;; 要素の削除
(Hash "d" nil)
;; 要素の巡回
(dotree (x Hash)
  (println x "=>" (eval x)))

比較演算

newLISPでは数値の比較、文字列の比較、リストの比較などを 全て=,<,>,<=,>=,!=で行います。
それぞれの比較関数は1個以上の引数を持ちます。
※一概には言えませんが比較の判定は結構ゆるいです。
;; 数値
(= 3.14 3.14)                   ;=> true
(!= (+ 3 4) 10)                 ;=> true
(<= 1 2 3 4 4 5)                ;=> true
(<= 1 3 2 4 5 4)                ;=> nil
;; 文字列
(< "Ada" "Basic" "C" "C++")     ;=> true
(= "lisp" "lisp")               ;=> true
(= "lisp" "LISP")               ;=> nil (大文字小文字は区別される)
;; リスト
(= '(a b c) '(a b c))           ;=> true
;; シンボルなどのオブジェクト
(= 'x 'x)                       ;=> true
(= 'x 'X)                       ;=> nil (シンボルの大文字小文字も区別される)

制御文

newLISPでは値が偽となるものはnilと空リスト()の2種類のみで、 それ以外の値は全て真となります(0や空文字列も真)。

条件文

(if 条件式
    真ならば実行
  偽ならば実行)
;; if-else文
(if (< x 0) "negative"
    (< x 10) "small"
    (< x 20) "midium"
    (<= 30 x) "big"
  "n/a")
(cond
  (条件式A 実行する処理1 ...)
  (条件式B ...)
  (条件式C ...)
  ...
  (true その他の処理 ...))
(case n
  (1 "one")
  (2 "two")
  (3 "three")
  (true "else"))

繰り返し

(while 終了条件
  ...)
;; C言語の for (i=-10; i<=10; i+=0.2) { ... } とほぼ等価
(for (x -10 10 0.2)
  (println (format "(x,y)=(%g,%g)" x (sin x))))
(dolist (i リスト [終了条件])
  ...)
(dotimes (i 10 (< 5 i))
  (println (format "[%d]Hello" i)))
;-> [0]Hello
;   [1]Hello
;   [2]Hello
;   [3]Hello
;   [4]Hello
;   [5]Hello
また各種ループマクロでは繰り返しの回数をシンボル$idxで参照できます。
(dostring (c "newLISP")
  (println (format "%d: %c" $idx c)))
;-> 0: n
;   1: e
;   2: w
;   3: L
;   4: I
;   5: S
;   6: P

例外処理とcatch/throw

(catch
    (dolist (x '(a b c d e))
      (if (= x 'd)
          (throw 'found))
      (println x)))
(or (open ファイル名 "read")
    ;; ファイルが存在しなければ例外を投げる
    (throw-error "file not exists."))
newLISPには例外処理に必須のfinally構文がありませんが catchフォームで代用することが可能です。
(しかしなぜunwind-protectがないのだろうか...)
(if (catch (/ (+ 1 2 3 4 5) 0) 'res)
    ;; 通常評価
    (println "result=>" res)
  (begin
    ;; エラー時に実行する処理
    (println "ゼロで除算しました")
    ;; 再度エラーを投げる
    (throw-error res)))

関数

関数は次のように定義します。
(define (add1 x)
  (+ x 1))
(add1 5)                        ;=> 6
引数が渡されなかった場合のデフォルト値を指定することも出来ます。
(define (foo x (y 1))
  (+ x y))
(foo 2 3)                       ;=> 5 (x=2, y=3)
(foo 2)                         ;=> 3 (x=2, y=1)
余分に受け取った引数(可変長引数)は関数argsで参照します。
(define (bar a b c)
  (list a b c (args)))
(bar 0 1 2 3 4 5)
;=> (0 1 2 (3 4 5))

ファイル入出力

デバイス

入出力ではC言語のような原始的なファイルディスクリプタを使用します。 newLISPではこのデバイスを切り替えて入出力を行います。
;; デフォルトのデバイス
(device)                        ;=> 0
;; 標準エラーに書き込む (標準エラーのデバイス番号は2)
(write-buffer 2 "Error occured")
;; ファイルに Hello World! を書き込む
(device (open ファイル名 "w"))
(println "Hello World!")
(close (device))
;; ファイルの中身を全て大文字にして出力する
(let ((fd (open ファイル名 "r"))
      (stdout 1))
  (while (read-line fd)
    (write-line stdout (upper-case (current-line))))
  (close fd))
もちろん、デバイスを利用しないファイル操作用関数もあります。
;; ファイルの読み込み
(read-file ファイル名)
;; ファイルの書き込み
(write-file ファイル名 "書き込みたい文字列")
;; ファイルの追加書き込み
(append-file ファイル名 "追加したい文字列")

入力

(read-char stdin)
(read-line stdin)
(read-key)

その他の文法

コンテキスト

コンテキストは名前空間のようなもので、これを利用してハッシュを作成したり FOOPを実現したりしています。
起動時に存在するコンテキストはMAIN,Class,Treeです。 コンテキスト名とシンボル名はコロン(:)で区切ります。
> (setq foo "MAIN's foo")
"MAIN's foo"
> (context 'newCtx)     ; コンテキスト切り替え
newCtx
newCtx> foo
nil
newCtx> (setq foo "newCtx's foo")
"newCtx's foo"
newCtx> MAIN:foo
"MAIN's foo"

FOOP

newLISPではFOOP(Functional Object-Oriented Programming)という テンプレートベースのオブジェクト指向プログラミングをサポートしています。
### この項目は筆者の都合により割愛されました ###

Implicit indexing

文字列とリスト、配列では要素の取得にインデックスを指定できます。
インデックスに負の値を指定すると反対側から順に要素を調べます。
;; リスト
(setq lst '(a b c d))
(lst 2)                         ;=> c == (nth 2 lst)
("newLISP" 0)                   ;=> "n" == (first "newLISP")
(0 4 "newLISP")                 ;=> "newL" == (slice "newLISP" 0 4)
;; 文字列
(setq str "newLISP")
(str -1)                        ;=> "P"
(1 str)                         ;=> "ewLISP"
;; 配列
(setq arr (array 2 3 (sequence 0 6))) ;=> ((0 1 2) (3 4 5))
(arr 1 2)                             ;=> 5
(setf (arr 1 2) (+ $it 10))           ;=> 15
arr                                   ;=> ((0 1 2) (3 4 15))

コマンドライン引数

(main-args)                     ;=> ("/usr/bin/newlisp" "-n" "-C" "-s10000")
(main-args 0)                   ;=> "/usr/bin/newlisp"

参考文献

参考リンク

以上の文章は以下の「○○基礎文法最速マスター」シリーズに触発されて書いたものです。

0 件のコメント:

コメントを投稿