lispで言語実装(4)

電卓の実装(4) 番外編

前回までのコードでは、「-1+2」の様な計算ができない。本当は字句解析で「-1」というトークンを返すようにするのがいいのかもしれないが、せっかくBNF構文解析を記述できるようにしたので、Fの定義を以下のように変更して対応してみる。

変更前: (setq F '((! NUM ("(" E ")")))) 
変更後: (setq F '((! NUM ((! "-" "+") NUM) ("(" E ")"))))

構文解析時に「-1」を(- 0 1)に展開するようにする。

実際に動作するコードは以下から。

(progn
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;; 字句解析
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (setq *src-stream nil); ソースとなるストリーム
  (setq unget-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-list))
        (let ((c (read-char *src-stream nil)))
          (cond ((equal c nil) nil)
                ((digit-char-p c) (let ((res (digit-char-p c)) (next))
                                    (while (and (setq next (peek *src-stream)) (digit-char-p next))
                                      (setq res (+ (* res 10)
                                                   (digit-char-p (read-char *src-stream nil))))
                                      )
                                    res))
                ((char= c #\SPC)(get-token))
                ((char= c #\TAB)(get-token))
                ((char= c #\RET)(get-token))
                (t (format nil "~A" c))))
      (pop unget-list)))

  (defun unget-token(tok)
    ;トークンを差し戻す関数。
    (push tok unget-list))

  (defun peek-token()
    ;トークンを覗き見る関数。無い場合はnilを返す。
    (let ((tok (get-token)))
      (unget-token tok)
      tok))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;; 構文解析
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (defun stringmatch (rule-str)
    ;ルール上の文字列("+"とか)とトークンを比較する。同じならばトークンを返す。
    (if (equal rule-str (peek-token))
        (list (get-token))))

 
  (defun *func (rule)
    ;*記号を実現する。
    (!func (list (cons '+ rule) 'EMPTY)))
  
  (defun +func (rule)
    ;+記号を実現する。
    (let ((tree (&func rule)))
      (if tree
          (let ((resTree tree))
            (while (setq tree (callrule rule))
              (unless (eq 'empty tree)
                (setq resTree (append resTree tree))))
            resTree))))


  (defun make-node (in)
    ; 中置記法を前置記法に変換
    (defun make-node-sub (left op-rights)
      (if (equal nil op-rights)
          left
        (make-node-sub (list (first op-rights) left (second op-rights))
                       (cddr op-rights))))
    (list (make-node-sub (car in) (cdr in))))

  (defun callrule(match)
    (cond ((symbolp match) (if (listp (symbol-value match))
                               (funcall (gethash match action) (&func (symbol-value match)))
                             (funcall (symbol-value match))))
          ((stringp match) (stringmatch match))
          ((equal '+ (car match)) (+func (cdr match)))
          ((equal '* (car match)) (*func (cdr match)))
          ((equal '! (car match)) (!func (cdr match)))
          (t                      (&func match))))

  (defun !func (rulelist)
    ;|記号を実現する。
    (dolist (rule rulelist nil)
      (let ((tree (callrule (list rule))))
        (when tree
          (return tree)))))

  (defun &func (rulelist)
    ;|記号を実現する。
    (let ((tree (callrule (car rulelist)))) ; 先頭のルールを適応
      (when tree                            ; 結果(tree)が nil でない場合
        (let ((resTree tree))               ; resTree に代入
          (dolist (rule (cdr rulelist) resTree) ; 2番目以降のルールを適応するループ
            (setq tree (callrule rule))         ; ルール適応し、結果をtreeに代入。
            (cond ((equal tree nil) (throw 'label1 'sytax-error)) ; 結果が nil ならばエラー。
                  ((equal tree 'empty))                           ; 結果が emptyならば何もしない。
                  (t (setq resTree (append resTree tree)))))))))  ; それ以外ならば追加する。


  ;;; 文法定義
  (setq EMPTY (lambda() 'empty))
  (setq NUM   (lambda()
                (if (numberp (peek-token))
                    (list (get-token)))))
  
  (setq E '(T (* (! "+" "-") T)))
  (setq T '(F (* (! "*" "/") F)))
;  (setq F '((! NUM ("(" E ")"))))
  (setq F '((! NUM ((! "-" "+") NUM) ("(" E ")"))))

  (setq action (make-hash-table :test #'equal))
  (setf (gethash 'E action) (lambda (token-list)
                              (make-node token-list)))
  (setf (gethash 'T action) (lambda (token-list)
                              (make-node token-list)))
  (setf (gethash 'F action) (lambda (token-list)
                              (cond ((= 3 (length token-list)) (list (second token-list)))
                                    ((= 2 (length token-list)) (list (list (first token-list) 0 (second token-list))))
                                    (t (make-node token-list)))))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;;; 意味解析
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (defun eval-tree(tree)
    (if (atom tree)
        tree
      (if (= 1 (length tree))
          (eval-tree (car tree))
        (cond ((equal (car tree) "+") (+ (eval-tree (second tree))
                                         (eval-tree (third tree))))
              ((equal (car tree) "-") (- (eval-tree (second tree))
                                         (eval-tree (third tree))))
              ((equal (car tree) "*") (* (eval-tree (second tree))
                                         (eval-tree (third tree))))
              ((equal (car tree) "/") (/ (eval-tree (second tree))
                                         (eval-tree (third tree))))))))

  (defun parse(start)
    (make-node (callrule E)))

  (format t "start~%")
  (setq src "(-1+2)*2")
  (set-src-stream (make-string-input-stream src))
  (setq s-tree (parse E))

  (format t "src: ~A~%" src)
  (format t "tree: ~A~%" s-tree)
  (format t "ans: ~A~%" (eval-tree s-tree))
  )