● 字句解析 文字列 --> トークン・ストリーム * 文字列をトークン(token)に区切る。 * トークンを認識し字句値を求める。 識別子や定数は、正則言語として定義される。 id = [A-Za-z][A-Za-z0-9]* num = 0 | [1-9][0-9]* 識別子のいくつかはキーワードとする if while ... 区切り記号 ( ) $ ... 演算子 + - * / = < > <= >= := ... 空白文字 スペース タブ 改行 トークンの間には(複数個の)空白文字を挿入することができる。 隣合う識別子、キーワード、定数の間には、 一つ以上の空白文字をいれなければならない。 * 字句解析の実際(Emacs Lispの場合) ;;; キーワードの連想リスト: ;;; ((キーワード文字列 . トークン) ... ) (defvar *keyword-alist* '(("if" . if) ("then" . then) ("else" . else) ("while" . while) ("do" . do))) ;;; トークンの連想リスト ;;; ((トークン文字列 . トークン) ... ) (defvar *token-alist* '(("(" . lpar) (")" . rpar) ("+" . +) ("-" . -) ("*" . *) ("/" . /) ("=" . =) ("<" . <) (">" . >) ("<=" . <=) (">=" . >=) (":=" . :=) ("$" . $))) ;;; カーソルの次のトークンを読んで、トークン値を返す。 ;;; カーソルはトークンの次に進む。 (defun next-token () ;; 空白文字(スペース,タブ,改行)を読み飛ばす。 (skip-chars-forward " \t\n") ;; 次の文字を調べる。 (let ((p (point)) (c (following-char))) (cond ((or (and (<= ?a c) (<= c ?z)) (and (<= ?A c) (<= c ?Z))) ;; 次の文字が英字ならば、栄数字を読み飛ばす。 (skip-chars-forward "A-Za-z0-9") ;; 文字列を抜き出して、キーワードの連想リストを調べる。 (let* ((id (buffer-substring p (point))) (a (assoc id *keyword-alist*))) (if a ;; 連想リストにあれば、 ;; キーワードに対応するトークンを返す。 (cons (cdr a) nil) ;; 連想リストになければ、 ;; (id . 文字列) というトークン値を返す。 (cons 'id id)))) ((= c ?0) ;; 次の文字が0ならば、(num . 0) というトークン値を返す。 (forward-char) (cons 'num 0)) ((and (<= ?1 c) (<= c ?9)) ;; 次の文字が1から9ならば、数字を読み飛ばす。 (skip-chars-forward "0-9") ;; 文字列を抜き出して、数に変換し、 ;; (num . 数) というトークン値を返す。 (cons 'num (string-to-int (buffer-substring p (point))))) (t ;; 特殊記号を読み飛ばす。 (skip-chars-forward "---()+*/=<>:$") ;; 文字列を抜き出して、トークンの連想リストを調べる。 (let* ((token (buffer-substring p (point))) (a (assoc token *token-alist*))) (if a ;; 連想リストにあれば、トークン値を返す。 (cons (cdr a) nil) ;; 連想リストになければ、エラー。 (error "%s: unrecognized token" token)))))))