Go言語側で、割り算関数を定義する。gmnlisp においての割り算には整数割り算の (div) と、浮動小数点型実数による割り算の(quotient) が存在する。今回は (div) のオリジナル版を作成する。
割り算を取り上げたのは、割り算ではゼロ除算というエラーケースがあるためだ。このエラーはGo言語のエラー処理でも、ISLisp のエラー処理のどちらでも扱うことができる。
package main import ( "context" "errors" "fmt" "github.com/hymkor/gmnlisp" ) // 独自に作ったゼロ割り算エラー var errDivZero = errors.New("division by zero") // 整数の割り算を行う関数。標準にも同じ関数があるが、Go と ISLisp では、 // マイナスの時の結果が微妙に違うので、一応、意味がある func divAndMod(ctx context.Context, w *gmnlisp.World, first, second gmnlisp.Node) (gmnlisp.Node, error) { firstValue, err := gmnlisp.ExpectClass[gmnlisp.Integer](ctx, w, first) if err != nil { return nil, err } secondValue, err := gmnlisp.ExpectClass[gmnlisp.Integer](ctx, w, second) if err != nil { return nil, err } if secondValue == 0.0 { return nil, errDivZero } return &gmnlisp.Cons{ Car: firstValue / secondValue, Cdr: firstValue % secondValue, }, nil } func main() { // gmnlisp のインタプリタのインスタンスを作成する lisp := gmnlisp.New() // 局所変数を定義する。(let ((a 10) (b 3)) .. ) と等価 // 変数は戻り値のインスタンス内でのみ有効となる lisp = lisp.Let( gmnlisp.Variables{ gmnlisp.NewSymbol("a"): gmnlisp.Integer(10), gmnlisp.NewSymbol("b"): gmnlisp.Integer(3)}) // NewSymbol はシンボル(IDをもった識別子)を定義する。 // 同じ名前であれば常に一意のID(番号)がふられる // 局所関数を定義する (labels ((div-and-mod (first second) ... )) ) と等価 // 関数は戻り値のインスタンス内でのみ有効となる lisp = lisp.Flet( gmnlisp.Functions{ gmnlisp.NewSymbol("div-and-mod"): gmnlisp.Function2(divAndMod)}) // Function2 は引数二つの関数をオブジェクト化する // 他に引数1個のFunction1, 任意引数数の &Function{} // マクロや特殊関数用の SpecialF などもある ctx := context.Background() // 実行させる Lisp コードのリスト cases := []string{ // 変数 a の内容を表示 `(format (standard-output) "A=~S~%" a)`, // 変数 b の内容を表示 `(format (standard-output) "B=~S~%" b)`, // Goで定義した関数を呼び出す "(div-and-mod a b)", // 割る数を 0 にして、わざとエラーをひきおこさせる "(setq b 0) (div-and-mod a b)", // Go関数で投げたエラーを Lisp 側の構文でとらえることができる // ( エラーオブジェクトが Node interface を実装していたら、 // Condition として細かく吟味することも可能だが、ここでは省略) `(catch 'fail (with-handler (lambda (c) (throw 'fail "Lisp program can catch errors")) (div-and-mod a b)))`, } for _, case1 := range cases { fmt.Printf("%s\n", case1) rv, err := lisp.Interpret(ctx, case1) if err != nil { fmt.Printf("--> FAIL: %s\n", err.Error()) // エラーはスタックトレース情報でラッピングされているが、 // errors.Is や errors.As でエラーを照合できる if errors.Is(err, errDivZero) { fmt.Println("--> Go program can catch errors\n") } fmt.Println() } else { fmt.Printf("--> PASS: %#v\n\n", rv) } } }
実行結果:
> ./gmnlisp-sample.exe
(format (standard-output) "A=~S~%" a)
A=10
--> PASS: gmnlisp.nullType{}
(format (standard-output) "B=~S~%" b)
B=3
--> PASS: gmnlisp.nullType{}
(div-and-mod a b)
--> PASS: (3 . 1)
(setq b 0) (div-and-mod a b)
--> FAIL: division by zero
at (DIV-AND-MOD A B)
--> Go program can catch errors
(catch 'fail
(with-handler
(lambda (c) (throw 'fail "Lisp program can catch errors"))
(div-and-mod a b)))
--> PASS: "Lisp program can catch errors"