lispで言語実装
新年だし、言語実装の勉強をはじめよう。ベース言語にはlispを選択。以下の手順で進めようと考えている。
- 電卓の実装
- TinyCの実装
- 若干実用的な言語の実装
電卓の実装(1) 字句解析
「(100+3)*10」のような入力に対して「1030」を返す電卓を作成する。言語実装セオリーに沿って、以下の順番で作成する。
- 字句解析 ← いまここ
- 構文解析
- 意味解析
普通、電卓程度では、ここまで分割しないと思われるが、勉強もかねてこのようにしたい。
字句解析に必要な関数は以下の4つ。
- set-src-stream … ソースとなる文字列(ストリーム)を登録する
- get-token … トークンを取得する
- unget-token … トークンを差し戻す
- peek-token … 先頭のトークンをのぞき見る
これらを以下のように実装した。
(progn ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; 字句解析 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (setq *src-stream nil); ソースとなるストリーム (setq *unget-token-list nil); unget-tokenされたトークンを保存するリスト (defun set-src-stream(stm) ;ソースとなるストリームをセット。 (setq *src-stream stm)) (defun peek(st) ;ストリームを覗き見る関数。 (let ((c (read-char st nil))) (if c (unread-char c st)) c)) (defun get-token() ;トークンを取得。無い場合はnilを返す。 (if (= 0 (length *unget-token-list)) (let ((c (read-char *src-stream nil))) (cond ((equal c nil) nil) ((digit-char-p c) (let ((res 0)) (while (progn ;do〜whileイデオム (setq res (+ (* res 10) (digit-char-p c))) (setq c (read-char *src-stream nil)) (and c (digit-char-p c))));継続条件 (if c (unread-char c *src-stream)) res)) ((member c '(#\SPC #\TAB #\RET #\LFD)) (get-token)) (t (format nil "~A" c)))) (pop *unget-token-list))) (defun unget-token(tok) ;トークンを差し戻す。 (push tok *unget-token-list)) (defun peek-token() ;トークンを覗き見る関数。無い場合はnilを返す。 (let ((tok (get-token))) (unget-token tok) tok)) (setq src "(-100 \n\t + +3)*-10") (set-src-stream (make-string-input-stream src)) (while (print (get-token))) )
出力結果
"(" "-" 100 "+" "+" 3 ")" "*" "-" 10 nil nil