;;; lalr.lisp ;;; ;;; This is an LALR parser generator. ;;; (c) 1988 Mark Johnson. mj@cs.brown.edu ;;; This is *not* the property of Xerox Corporation! ;; This is a modified version of the lalr.cl as found with the CMU AI Repository at ;; ;; Used with permission under the MIT license: ;; Permission is hereby granted, free of charge, to any person obtaining ;; a copy of this software and associated documentation files (the ;; "Software"), to deal in the Software without restriction, including ;; without limitation the rights to use, copy, modify, merge, publish, ;; distribute, sublicense, and/or sell copies of the Software, and to ;; permit persons to whom the Software is furnished to do so, subject to ;; the following conditions: ;; ;; The above copyright notice and this permission notice shall be ;; included in all copies or substantial portions of the Software. ;; ;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. ;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY ;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ;;; Modified to cache the first terminals, the epsilon derivations ;;; the rules that expand a category, and the items that expand ;;; a category ;;; There is a sample grammar at the end of this file. ;;; Use your text-editor to search for "Test grammar" to find it. ;;; (in-package 'LALR) ;;; (export '(make-parser lalr-parser *lalr-debug* grammar lexforms $ parse)) ;;; (shadow '(first rest)) ;;; (defmacro first (x) `(car ,x)) ;;; (defmacro rest (x) `(cdr ,x)) ;;; The external interface is MAKE-PARSER. It takes three arguments, a ;;; CFG grammar, a list of the lexical or terminal categories, and an ;;; atomic end marker. It produces a list which is the Lisp code for ;;; an LALR(1) parser for that grammar. If that list is compiled, then ;;; the function LALR-PARSER is defined. LALR-PARSER is a function with ;;; two arguments, NEXT-INPUT and PARSE-ERROR. ;;; ;;; The first argument to LALR-PARSER, NEXT-INPUT must be a function with ;;; zero arguments; every time NEXT-INPUT is called it should return ;;; a CONS cell, the CAR of which is the category of the next lexical ;;; form in the input and the CDR of which is the value of that form. ;;; Each call to NEXT-INPUT should advance one lexical item in the ;;; input. When the input is consumed, NEXT-INPUT should return a ;;; CONS whose CAR is the atomic end marker used in the call to MAKE-PARSER. ;;; ;;; The second argument to LALR-PARSER, PARSE-ERROR will be called ;;; if the parse fails because the input is ill-formed. ;;; ;;; ;;; There is a sample at the end of this file. ;;;; Modifications ;; - Renamed specials to wear ear muffs ;; - Put into a package ;; - Support for operator precedence ;; - Considerable speedup in table construction ;; - Table-based driver ;; --Gilbert Baumann #+(or) (defpackage :de.bauhh.lalr (:use :cl) (:export #:make-parser ;This is there for olde PARSE macro --- which needs to go away #:define-grammar ;This should go away with time too. #:lalr-parse #:lalr-table ;; #:make-lalr-table #:lalr-table-p #:lalr-table-topcat #:lalr-table-states #:lalr-state #:make-lalr-state #:lalr-state-p #:lalr-state-name #:lalr-state-transitions #:lalr-action #:make-lalr-action #:lalr-action-categories #:lalr-action-goto #:reduce-action #:make-reduce-action #:reduce-action-p #:reduce-action-npop #:reduce-action-function #:shift-action #:make-shift-action #:shift-action-p)) #+(or) (in-package :de.bauhh.lalr) (in-package :nyala) (eval-when (:compile-toplevel) (declaim (optimize (speed 3) (safety 1)))) (defvar *conflict-resolver* nil) ;;; definitions of constants and global variables used (defvar *topcat* '$Start) (defvar *end-marker*) (defvar *lex* nil "List of all lexemes (tokens). Usually keywords, but could be anything.") (defvar *lex-vector* nil "Simple vector mapping a lexeme id to the lexeme itself.") (defvar *lex-index* nil "The inverse of *LEX-VECTOR*. Mapping a lexeme to it's id.") (defvar *lex-spelling* nil "An hash table mapping a lexeme keyword to its spelling, if known.") (defvar *rules*) (defvar *nrules*) (defvar *start*) (defvar *starts*) (defvar *cats*) (defvar *firsts*) (defvar *epsilons*) (defvar *expansions*) (defvar *lalr-debug* NIL "Inserts debugging code into parser if non-NIL") (defvar *state-list* '()) (defvar *state-hash*) (defvar *precedence*) (defparameter *next-state-no* -1) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Rules and Grammars ;;; (defstruct rule no mother daughters action options) (defmethod print-object ((object rule) stream) (print-unreadable-object (object stream :type t :identity nil) (format stream "~@<~S ~_-> ~<~@{~S~^ ~_~}~:>~:>" (rule-mother object) (rule-daughters object)))) (defun transform-rule (rule no) (destructuring-bind (lhs rhs action options) rule (make-rule :no no :mother lhs :daughters rhs :action action :options options))) (defun compute-expansion (cat) (remove-if-not #'(lambda (rule) (eq (rule-mother rule) cat)) *rules*)) (defmacro expand (cat) `(gethash ,cat *expansions*)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Properties of grammars (defun get-all-cats (start) (labels ((try (deja-vu cat) (if (find cat deja-vu) deja-vu (try-rules (cons cat deja-vu) (compute-expansion cat)))) (try-rules (deja-vu rules) (if rules (try-rules (try-cats deja-vu (rule-daughters (car rules))) (cdr rules)) deja-vu)) (try-cats (deja-vu cats) (if cats (try-cats (try deja-vu (car cats)) (cdr cats)) deja-vu))) (try '() start))) (defun derives-eps (c) "t if c can be rewritten as the null string" (labels ((try (deja-vu cat) (unless (find cat deja-vu) (some #'(lambda (r) (every #'(lambda (c1) (try (cons cat deja-vu) c1)) (rule-daughters r))) (expand cat))))) (try '() c))) (declaim (inline derives-epsilon)) (defun derives-epsilon (c) "looks up the cache to see if c derives the null string" (member c *epsilons* :test 'eq)) (declaim (inline cat-firsts)) (defun cat-firsts (cat) (gethash cat *starts*)) (defun first-terms (cat-list) "the leading terminals of an expansion of cat-list" (assert (= 1 (length cat-list))) (labels ((first-ds (cats) (if cats (if (derives-epsilon (car cats)) (cons (car cats) (first-ds (cdr cats))) (list (car cats))))) (try (deja-vu cat) (if (member cat deja-vu) deja-vu (try-list (cons cat deja-vu) (mapcan #'(lambda (r) (first-ds (rule-daughters r))) (expand cat))))) (try-list (deja-vu cats) (if cats (try-list (try deja-vu (car cats)) (cdr cats)) deja-vu))) (remove-if-not #'(lambda (term) (or (eq *end-marker* term) (find term *lex*))) (try-list '() (first-ds cat-list))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; LALR(1) parsing table constructor ;;; (defstruct (item (:constructor cons-item (rule pos la))) rule pos la) (defstruct citem rule pos la) (defmacro item-daughters (i) `(rule-daughters (item-rule ,i))) (defmacro citem-daughters (i) `(rule-daughters (citem-rule ,i))) (defmacro item-right (i) `(nthcdr (item-pos ,i) (item-daughters ,i))) (defmacro citem-right (i) `(nthcdr (citem-pos ,i) (citem-daughters ,i))) (defmacro item-equal (i1 i2) `(and (eq (item-rule ,i1) (item-rule ,i2)) (= (item-pos ,i1) (item-pos ,i2)) (eq (item-la ,i1) (item-la ,i2)))) (defun item-core-equal (c1 c2) "T if the cores of c1 and c2 are equal" (and (eq (item-rule c1) (item-rule c2)) (= (item-pos c1) (item-pos c2)))) (defun citem-core-equal (c1 c2) "T if the cores of c1 and c2 are equal" (and (eq (citem-rule c1) (citem-rule c2)) (= (citem-pos c1) (citem-pos c2)))) (defun citem-item-core-equal (c1 c2) "T if the cores of c1 and c2 are equal" (and (eq (citem-rule c1) (item-rule c2)) (= (citem-pos c1) (item-pos c2)))) (defun shift-citems (items cat) "shifts a set of items over cat" (labels ((shift-item (item) (if (eq (first (citem-right item)) cat) (make-citem :rule (citem-rule item) :pos (1+ (citem-pos item)) :la (citem-la item))))) (let ((new-items '())) (dolist (i items) (let ((n (shift-item i))) (if n (push n new-items)))) new-items))) (defun items-right (items) "returns the set of categories appearing to the right of the dot" (let ((right '())) (dolist (i items) (let ((d (first (item-right i)))) (if (and d (not (find d right))) (push d right)))) right)) (defun citems-right (items) "returns the set of categories appearing to the right of the dot" (let ((right '())) (dolist (i items) (let ((d (first (citem-right i)))) (and d (pushnew d right)))) right)) (defun compact-items (items) "collapses items with the same core to compact items" (let ((sofar '())) (dolist (i items) (let ((ci (dolist (s sofar) (if (citem-item-core-equal s i) (return s))))) (if ci (push (item-la i) (citem-la ci)) (push (make-citem :rule (item-rule i) :pos (item-pos i) :la (list (item-la i))) sofar)))) (sort sofar #'< :key #'(lambda (i) (rule-no (citem-rule i)))))) (defun expand-citems (citems) "expands a list of compact items into items" (let ((items '())) (dolist (ci citems) (dolist (la (citem-la ci)) (push (cons-item (citem-rule ci) (citem-pos ci) la) items))) items)) (defun subsumes-citems (ci1s ci2s) "T if the sorted set of items ci2s subsumes the sorted set ci1s" (and (= (length ci1s) (length ci2s)) (every #'(lambda (ci1 ci2) (and (citem-core-equal ci1 ci2) (subsetp (citem-la ci1) (citem-la ci2)))) ci1s ci2s))) (defun merge-citems (ci1s ci2s) "Adds the las of ci1s to ci2s. ci2s should subsume ci1s" (mapcar #'(lambda (ci1 ci2) (setf (citem-la ci2) (union (citem-la ci1) (citem-la ci2)))) ci1s ci2s) ci2s) ;;; The actual table construction functions (defstruct state name citems reduces shifts conflict) (defstruct shift cat where) ;(defun lookup (citems) ; "finds a state with the same core items as citems if it exits" ; (find-if #'(lambda (state) ; (and (= (length citems) (length (state-citems state))) ; (every #'(lambda (ci1 ci2) ; (item-core-equal ci1 ci2)) ; citems (state-citems state)) ; )) ; *state-list*)) #+(OR) (defun lookup (citems) "finds a state with the same core items as citems if it exits" (dolist (state *state-list*) (if (and (= (length citems) (length (state-citems state))) (do ((ci1s citems (cdr ci1s)) (ci2s (state-citems state) (cdr ci2s))) ((null ci1s) t) (unless (citem-core-equal (car ci1s) (car ci2s)) (return nil)))) (return state)))) (defun lookup (citems) "finds a state with the same core items as citems if it exits" (gethash (citems-key citems) *state-hash*)) (defun add-state (citems) "creates a new state and adds it to the state list" (let ((new-state (make-state :name (incf *next-state-no*) :citems citems))) (push new-state *state-list*) (setf (gethash (citems-key citems) *state-hash*) new-state) new-state)) (defun citems-key (citems) (mapcan (lambda (citem) (list (citem-rule citem) (citem-pos citem))) citems)) (defun get-state-name (citems) "returns the state name for this set of items" (let* ((state (lookup citems))) (cond ((null state) (setq state (add-state citems)) (build-state state citems)) ((subsumes-citems citems (state-citems state)) nil) (t (merge-citems citems (state-citems state)) (follow-state citems))) (state-name state))) (defun build-state (state citems) "creates the states that this state can goto" (let ((closure (close-citems citems))) (dolist (cat (citems-right closure)) (push (make-shift :cat cat :where (get-state-name (shift-citems closure cat))) (state-shifts state))))) (defun follow-state (citems) "percolates look-ahead onto descendant states of this state" (let ((closure (close-citems citems))) (dolist (cat (citems-right closure)) (get-state-name (shift-citems closure cat))))) (defun build-table (start) "Actually builds the table" (setq *state-list* '()) (setq *state-hash* (make-hash-table :test 'equal)) (setq *next-state-no* -1) (get-state-name (list (make-citem :rule (make-rule :no 0 :mother *topcat* :daughters (list start)) :pos 0 :la (list *end-marker*)))) (setq *state-list* (nreverse *state-list*))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; LALR(1) parsing table printer ;;; (defun print-table (*state-list*) "Prints the state table" (dolist (state *state-list*) (format t "~%~%~a:" (state-name state)) (dolist (citem (state-citems state)) (format t "~% ~a -->~{ ~a~} .~{ ~a~}, ~{~a ~}" (rule-mother (item-rule citem)) (subseq (rule-daughters (item-rule citem)) 0 (item-pos citem)) (subseq (rule-daughters (item-rule citem)) (item-pos citem)) (item-la citem))) (dolist (shift (state-shifts state)) (format t "~% On ~a shift ~a" (shift-cat shift) (shift-where shift))) (dolist (reduce (delete-if #'(lambda (i) (citem-right i)) (close-citems (state-citems state)))) (format t "~% On~{ ~a~} reduce~{ ~a~} --> ~a" (citem-la reduce) (rule-daughters (citem-rule reduce)) (rule-mother (citem-rule reduce))))) (format t "~%")) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; LALR(1) parser constructor ;;; ;;; next-input performs lexical analysis. It must return a cons cell. ;;; its car holds the category, its cdr the value. (defun close-citems (citems) "computes the closure of a set of items" (declare (optimize (speed 3) (safety 0))) (let ((sofar (make-array *nrules* :initial-element nil))) (declare (type (simple-array t (*)) sofar)) (let ((todo nil)) (declare (type list todo)) (dolist (q (expand-citems citems)) (push q todo)) (do () ((null todo)) (multiple-value-bind (rule pos la) (let ((i (pop todo))) (values (item-rule i) (item-pos i) (item-la i))) (let* ((rgt (nthcdr pos (rule-daughters rule)))) (when rgt (let ((las (first-terminals-1 (rest rgt) la))) (dolist (r (expand (first rgt))) (let ((ri (rule-no r))) (declare (type fixnum ri)) (let* ((q (or (svref sofar ri) (setf (svref sofar ri) (let ((new (make-citem :rule r :pos 0))) (push new citems) new))))) (let ((new-la (citem-la q))) (dolist (la las) (unless (member la new-la :test 'eq) (push la new-la) (push (cons-item r 0 la) todo))) (setf (citem-la q) new-la)))))))))))) (sort (copy-list citems) #'< :key #'(lambda (i) (rule-no (citem-rule i))))) (defun build-parser-table-1 (state-list) (mapcar #'translate-state-1 state-list)) ;; table ::= ( { }* ) ;; state ::= ( { }*) ;; transition ::= ( :SHIFT ) ;; | ( :REDUCE ) (defun translate-state-1 (state) "translates a state into lisp code that could appear in a labels form" (let ((reduces (state-reduces state)) (symbols-sofar '())) ; to ensure that a symbol never occurs twice (setf (state-reduces state) reduces) (labels ((translate-shift (shift) (push (shift-cat shift) symbols-sofar) `((,(shift-cat shift)) :shift ,(shift-where shift))) (translate-reduce (item) #+(or) ;this is obsolete (when (intersection (citem-la item) symbols-sofar) (fresh-line) (pprint-logical-block (*standard-output* nil :per-line-prefix ";; ") (format t "Warning, shift/reduce conflict: state ~a: ~a --> ~{~a ~} <*> ~{~a ~}~%" (state-name state) (rule-mother (citem-rule item)) (subseq (rule-daughters (citem-rule item)) 0 (citem-pos item)) (subseq (rule-daughters (citem-rule item)) (citem-pos item)) ) (format t "On ~@<~{~S~^, ~:_~}~:>" (citem-la item))) (terpri) (force-output) '(setf (citem-la item) (set-difference (citem-la item) symbols-sofar))) (dolist (la (citem-la item)) (push la symbols-sofar)) `(,(copy-list (citem-la item)) :reduce ,(rule-mother (citem-rule item)) ,(citem-pos item) ,(rule-action (citem-rule item))))) `(,(state-name state) ,@(mapcar #'translate-shift (state-shifts state)) ,@(mapcar #'translate-reduce reduces))))) (defstruct (lalr-table (:constructor cons-lalr-table)) topcat states) (defstruct lalr-state name transitions) (defstruct lalr-action categories goto) (defstruct (reduce-action (:include lalr-action)) npop function) (defstruct (shift-action (:include lalr-action)) ) ;;;;;;;;; (defun make-lalr-table (state-table) (cons-lalr-table :topcat (caar state-table) :states (coerce (mapcar (lambda (state) (destructuring-bind (no &rest transitions) state (make-lalr-state :name no :transitions (mapcar (lambda (tr) (destructuring-bind (cats kind &rest more) tr (ecase kind (:shift (destructuring-bind (goto) more (make-shift-action :categories cats :goto goto))) (:reduce (destructuring-bind (goto npop action) more (make-reduce-action :categories cats :goto goto :npop npop :function action)))))) transitions)))) state-table) 'vector))) (defun grammar-lalr-table (lex rules &key (eof :eof)) (make-lalr-table (grammar-state-table rules lex eof))) ;; There is a cache. If asked to compile the same grammar twice, we return ;; what we came up with. This speeds up development, when just the semantic ;; actions changed. (defvar *last-grammar-req* nil) (defun grammar-state-table (grammar lex end-marker) (let ((key (list grammar lex end-marker *conflict-resolver*))) (cdr (if (equal key (car *last-grammar-req*)) *last-grammar-req* (setq *last-grammar-req* (cons key (grammar-state-table-1 grammar lex end-marker))))))) (defun grammar-state-table-1 (grammar lex end-marker) ;; For debugging: (set' *grammar* (list grammar lex end-marker)) (set' *original-lex* lex) ;; (setq *end-marker* end-marker) (setq *precedence* nil) (setq *lex* nil) (setq *lex-spelling* (make-hash-table)) (dolist (k lex) (if (consp k) (destructuring-bind (kw spelling) k (push kw *lex*) (setf (gethash kw *lex-spelling*) spelling)) (push k *lex*))) ;; Pull precedence (setq grammar (remove-if (lambda (rule) (cond ((typep rule '(cons (member :precedence) list)) (dolist (prec (cdr rule)) (etypecase prec ((cons (member :left :right :nonassoc) list) (setq *precedence* (nconc *precedence* (list prec)))))) t) (t nil))) grammar)) ;; cache some data that will be useful later (setq *lex-vector* (coerce (cons *end-marker* *lex*) 'vector)) (setq *lex-index* (make-hash-table :test 'eq)) (loop for j from 0 for c across *lex-vector* do (setf (gethash c *lex-index*) j)) (setq *start* (caar grammar)) (setq *rules* (let ((i 0)) (mapcar #'(lambda (r) (transform-rule r (incf i))) grammar))) (setq *nrules* (1+ (length *rules*))) (setq *cats* (get-all-cats *start*)) (setq *expansions* (make-hash-table :test 'eq)) (dolist (cat *cats*) (setf (gethash cat *expansions*) (compute-expansion cat))) (setq *epsilons* (remove-if-not #'derives-eps *cats*)) (setq *starts* (make-hash-table :test 'eq)) (dolist (cat (cons *end-marker* *cats*)) (setf (gethash cat *starts*) (first-terms (list cat)))) ;; now actually build the parser (build-table *start*) (when (and (listp *lalr-debug*) (member 'print-table *lalr-debug*)) (print-table *state-list*)) (format t "~&Table ready, ~D rules, ~D states, ~D terminals.~%" *nrules* (length *state-list*) (length *lex*)) (force-output) (setq *state-list* (compute-reduces *state-list*)) (setq *state-list* (resolve-conflicts *state-list*)) (build-parser-table-1 *state-list*)) (defun first-terminals-1 (cat-list more &aux res) (declare (optimize (speed 3) (safety 0)) (type list cat-list)) (loop (cond ((null cat-list) (return (if res (union res (cat-firsts more)) (cat-firsts more)))) ((derives-epsilon (first cat-list)) (setf res (if res (union res (cat-firsts (first cat-list))) (cat-firsts (first cat-list)))) (setq cat-list (rest cat-list))) (t (return (if res (union res (cat-firsts (first cat-list))) (cat-firsts (first cat-list)))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; Test grammar and lexical analyser ;;; ;;; A Test grammar #+(or) (progn (lalr-define-grammar lalr-parser (det n v) (s --> np vp #'(lambda (np vp) (list 's np vp))) (np --> det n #'(lambda (det n) (list 'np det n))) (np --> #'(lambda () '(np))) (vp --> v np #'(lambda (v np) (list 'vp v np))) (vp --> v s #'(lambda (v s) (list 'vp v s)))) (defparameter *lexicon* '((the det) (man n) (woman n) (cat n) (dog n) (loves v) (thinks v) (hates v))) (defun parse (words) (labels ((lookup (word) (cadr (assoc word *lexicon*))) (next-input () (cond ((null words) :eof) (t (let* ((word (pop words)) (cat (lookup word))) (values cat ; category (list cat word)))))) ; value (parse-error (msg) (format nil "Error before ~a: ~a" words msg))) (lalr-parser #'next-input #'parse-error)))) ;;; *EOF* ;;;; -- Conflict Resolution ------------------------------------------------------------------- (defun compute-reduces (state-list) (dolist (state state-list state-list) (setf (state-reduces state) (delete-if #'(lambda (i) (citem-right i)) (close-citems (state-citems state)))))) (defun default-conflict-resolver (cat shifts reduces) (let ((candidates (append shifts reduces))) (cond ((and (= 0 (length shifts)) (= 1 (length reduces))) reduces) ((and (= 1 (length shifts)) (= 0 (length reduces))) shifts) ;; Shift/reduce conflict ((and (= (length candidates) 2) (shift-p (car candidates)) (item-p (cadr candidates))) (destructuring-bind (shift reduce) candidates (let ((shift-prec (precedence cat)) (reduce-prec (some #'precedence (item-daughters reduce)))) (let ((shift-rule-options (remove-if (lambda (x) (not (member (car x) '(:left :right :nonassoc)))) (mapcan (lambda (shift) (mapcan (lambda (x) (copy-list (rule-options x))) (mapcar 'citem-rule (state-citems (elt *state-list* (shift-where shift)))))) shifts))) (reduce-rule-options (remove-if (lambda (x) (not (member (car x) '(:left :right :nonassoc)))) (mapcan #'(lambda (x) (copy-list (rule-options (item-rule x)))) reduces)))) (let ((shift-prec (or (car shift-rule-options) shift-prec)) (reduce-prec (or (car reduce-rule-options) reduce-prec))) (cond ((and shift-prec reduce-prec (and (cadr shift-prec) (cadr reduce-prec)) (> (cadr shift-prec) (cadr reduce-prec))) (list shift)) ((and shift-prec reduce-prec (and (cadr shift-prec) (cadr reduce-prec)) (< (cadr shift-prec) (cadr reduce-prec))) (list reduce)) ((eq (car shift-prec) :left) (list reduce)) ((eq (car shift-prec) :right) (list shift)) ((eq (car shift-prec) :nonassoc) (list shift)) ((eq (car reduce-prec) :left) ;?? (list reduce)) ((eq (car reduce-prec) :right) ;?? (list shift)) (t (queue-report-conflict cat shifts reduces) #+NIL (list shift) candidates ))))))) (t ;; #-NIL (warn "Unresolvable conflict on ~S:~%~S" cat candidates) (queue-report-conflict cat shifts reduces) #+NIL (list (cond ((find-if #'shift-p candidates)) ((car (sort (copy-list candidates) #'< :key (lambda (i) (rule-no (item-rule i)))))))) candidates)))) (defvar *conflicts-found* nil "During RESOLVE-CONFLICTS all the conflicts found, so that a summary can be printed. List of ( ).") (defun resolve-conflicts (state-list &aux (*conflicts-found* nil)) (prog1 (loop for state in state-list for state-no from 0 collect (let* ((all-cat (remove-duplicates (append (mapcan (lambda (p) (copy-list (citem-la p))) (state-reduces state)) (mapcar #'shift-cat (state-shifts state))))) (new-transitions (loop for cat in all-cat append (let* ((shifts (remove cat (state-shifts state) :test-not 'eq :key #'shift-cat)) (reduces (remove-if-not (lambda (p) (eq cat (item-la p))) (expand-citems (state-reduces state))))) (if *conflict-resolver* (funcall *conflict-resolver* cat shifts reduces) (default-conflict-resolver cat shifts reduces)))))) (setq state (copy-state state)) (setf (state-shifts state) (remove-if-not #'shift-p new-transitions) (state-reduces state) (compact-items (remove-if-not #'item-p new-transitions))) state)) (cond ((null *conflicts-found*) (format t "~%;; No conflicts~%")) (t (let ((msg (with-output-to-string (bag) (dolist (k *conflicts-found*) (report-conflict (car k) (cadr k) (caddr k) bag))))) (warn "~D conflicts found, ~D reduce/reduce conflicts~%~A" (length *conflicts-found*) (count-if (lambda (q) (>= (length (third q)) 2)) *conflicts-found*) msg)))))) (defun queue-report-conflict (cat shifts reduces) (labels ((conflict-key (q) (destructuring-bind (cat-list shifts reduces) q (declare (ignore cat-list)) (list (mapcar (lambda (cand) (when (shift-p cand) (let ((citems (state-citems (elt *state-list* (shift-where cand))))) (mapcar (lambda (citem) (list (citem-rule citem) (citem-pos citem))) citems)))) shifts) (mapcar (lambda (item) (when (item-p item) (list (item-rule item) (item-pos item)))) reduces))))) (let ((q (find (conflict-key (list cat shifts reduces)) *conflicts-found* :key #'conflict-key :test #'equal))) (if q (pushnew cat (car q)) (push (list (list cat) shifts reduces) *conflicts-found*))))) (defun report-conflict (cat-list shifts reduces &optional (stream *standard-output*)) (when '(> (length reduces) 1) ;; This really needs to be nicer. (let ((*print-pretty* t) (*print-right-margin* 120)) (format stream "~&~%=== Unresolvable shift/reduce conflict on ~@<~{~S~^, ~:_~}~:>:~%" (mapcar #'spelling cat-list)) (when shifts (format stream "Shifts:~%") (dolist (cand shifts) (when (shift-p cand) (dolist (citem (state-citems (elt *state-list* (shift-where cand)))) (write-string " " stream) (show-citem citem stream) (terpri stream))))) (when reduces (format stream "Reduces:~%") (dolist (cand reduces) (cond ((item-p cand) (write-string " " stream) (show-item cand stream) (terpri stream)))))))) (defun show-citem (citem &optional (stream *standard-output*)) (show-item-1 (citem-rule citem) (max 0 (1- (citem-pos citem))) stream)) (defun show-item (item &optional (stream *standard-output*)) (show-item-1 (item-rule item) (item-pos item) stream)) (defun show-item-1 (rule pos stream) (let* ((daughters (rule-daughters rule))) (format stream "~A -> ~{~S~^ ~} . ~{~S~^ ~}" (rule-mother rule) (mapcar #'spelling (subseq daughters 0 pos)) (mapcar #'spelling (subseq daughters pos))))) #+NIL (defun conflict-key (q) (destructuring-bind (cat-list shifts reduces) q (declare (ignore cat-list)) (with-output-to-string (stream) (when shifts (format stream "Shifts:~%") (dolist (cand shifts) (when (shift-p cand) (dolist (citem (state-citems (elt *state-list* (shift-where cand)))) (write-string " " stream) (show-citem citem stream) (terpri stream))))) (when reduces (format stream "Reduces:~%") (dolist (cand reduces) (cond ((item-p cand) (write-string " " stream) (show-item cand stream) (terpri stream)))))))) (defun spelling (cat) (gethash cat *lex-spelling* cat)) ;; ;; (:precedence { :left | :right | :nonassoc } lexeme...) ;; (defun precedence (lexeme) ;; -> ( ) (do ((i 0 (1+ i)) (p *precedence* (cdr p))) ((null p) nil) (when (member lexeme (cdar p)) (return (list (caar p) i))))) ;;;; ------------------------------------------------------------------------------------------ (defun lalr-parse (table next-input &optional action-vector) ;; (declare (optimize (speed 3) (safety 0))) (LET ((VAL-STACK '()) ;value stack (STATE-STACK '()) ;state stack (CUR-STATE 0) ;current state CAT VAL ACTION) (TAGBODY (SETF (VALUES CAT VAL) (FUNCALL NEXT-INPUT)) :DISPATCH (SETQ ACTION (FIND CAT (LALR-STATE-TRANSITIONS (SVREF (LALR-TABLE-STATES TABLE) CUR-STATE)) :KEY #'LALR-ACTION-CATEGORIES :TEST #'MEMBER)) (ETYPECASE ACTION (SHIFT-ACTION (PUSH CUR-STATE STATE-STACK) (PUSH VAL VAL-STACK) (SETF CUR-STATE (SHIFT-ACTION-GOTO ACTION)) (SETF (VALUES CAT VAL) (FUNCALL NEXT-INPUT)) (GO :DISPATCH)) (REDUCE-ACTION (WHEN (EQL (REDUCE-ACTION-GOTO ACTION) *TOPCAT*) ;hmm (RETURN-FROM LALR-PARSE (POP VAL-STACK))) (LET ((DAUGHTER-VALUES '())) (DOTIMES (I (THE FIXNUM (REDUCE-ACTION-NPOP ACTION))) (PUSH (POP VAL-STACK) DAUGHTER-VALUES) (SETQ CUR-STATE (POP STATE-STACK))) (PUSH CUR-STATE STATE-STACK) (LET* ((FUN (LET ((FUN (REDUCE-ACTION-FUNCTION ACTION))) (IF (INTEGERP FUN) (ELT ACTION-VECTOR FUN) FUN)))) (PUSH (APPLY FUN DAUGHTER-VALUES) VAL-STACK)) (SETQ ACTION (FIND (REDUCE-ACTION-GOTO ACTION) (LALR-STATE-TRANSITIONS (SVREF (LALR-TABLE-STATES TABLE) CUR-STATE)) :KEY #'LALR-ACTION-CATEGORIES :TEST #'MEMBER)) (SETF CUR-STATE (SHIFT-ACTION-GOTO ACTION)) (GO :DISPATCH))) (T (error "parse error~%LA = ~S~%STATE = ~S~%" CAT CUR-STATE)) )))) ;;;; -- TOOD ---------------------------------------------------------------------------------- ;; - Either allow for arbitrary lexemes (including strings) or have some hook ;; for spelling a lexeme. ;; - Precedence for an operator alone is not enough. They at times are heavily ;; overloaded. So we would need means to specify the precedence of a given ;; token. Something like: ;; (expr -> expr (:left "+") expr) ;; This implies that precedence is not per token, but per mentioning of a token. ;; Also: Switch GRAMMAR-STATE-TABLE to accepting something more like: ;; ( ( { }* ) ) ;; What I'd rather have is a function taking: ;; rules -- a list of ( ( { }* ) ) ;; returning a table as mapped out above. ;; Surface syntax, conflict resolution, and driver needs to happen outside. ;; We would need some means to have local precedence. Maybe: ;; (expr -> expr (:left "+") expr) ;; It must be possible to apply a precedence to an individual token. ;; Like stmt -> "if" expr "then" stmt (:left "else") stmt ;; Borrow from Prolog? expr -> expr (:yfx "+") expr ;; Maybe we pull this as RULE-OPTIONS = (:prec 1 :left 100) ;; RULE-PRECEDENCES -> ((1 :left 100)) ;; ;; expr -> expr (:left) "+" expr ;; expr -> "if" expr "then" expr (:nonassoc) "then" expr ;; ;; Anyhow, we _need_ means to have rhs being some data object. ;; - ;;;; ------------------------------------------------------------------------------------------ #+(or) (defun lalr-table-3 (rules lexemes end-marker) (setq *end-marker* end-marker) (setq *precedence* nil) (setq *lex* nil) (setq *lex-spelling* (make-hash-table)) (dolist (k lexemes) (if (consp k) (destructuring-bind (kw spelling) k (push kw *lex*) (setf (gethash kw *lex-spelling*) spelling)) (push k *lex*))) ;; cache some data that will be useful later (setq *lex-vector* (coerce (cons *end-marker* *lex*) 'vector)) (setq *lex-index* (make-hash-table :test 'eq)) (loop for j from 0 for c across *lex-vector* do (setf (gethash c *lex-index*) j)) (setq *start* (caar rules)) (setq *rules* (let ((i 0)) (mapcar (lambda (r) (make-rule :no (1- (incf i)) :mother (car r) :daughters (cdr r))) rules))) (setq *nrules* (length *rules*)) (setq *cats* (get-all-cats *start*)) (setq *expansions* (make-hash-table :test 'eq)) (dolist (cat *cats*) (setf (gethash cat *expansions*) (compute-expansion cat))) (setq *epsilons* (remove-if-not #'derives-eps *cats*)) (setq *starts* (make-hash-table :test 'eq)) (dolist (cat (cons *end-marker* *cats*)) (setf (gethash cat *starts*) (first-terms (list cat)))) ;; now actually build the parser (build-table *start*) (when (and (listp *lalr-debug*) (member 'print-table *lalr-debug*)) (print-table *state-list*)) (format t "~&Table ready, ~D rules, ~D states, ~D terminals.~%" *nrules* (length *state-list*) (length *lex*)) (force-output) (setq *state-list* (compute-reduces *state-list*)) '(setq *state-list* (resolve-conflicts *state-list*)) *state-list*) ;; We could state that each rule has a precedence and and optionally an ;; associativity. A conflict is between two rules. If rules have different ;; precedence the rule with the higher precedence reduces, the other shifts. ;; If both rules have the same precedence then the associativity says whether ;; we shift or reduce. ;; Some reduces are harmless for they return the same value. #+NIL ;; Why not? (defclass production () ((lhs :initarg :lhs :initform nil :accessor production-lhs) (rhs :initarg :rhs :initform nil :accessor production-rhs) (action :initarg :action :initform nil :accessor production-action) (plist :initarg :plist :initform nil :accessor production-plist)))