;;; ebnf-otz.el --- syntactic chart OpTimiZer

;; Copyright (C) 1999-2019 Free Software Foundation, Inc.

;; Author: Vinicius Jose Latorre <viniciusjl@ig.com.br>
;; Maintainer: Vinicius Jose Latorre <viniciusjl@ig.com.br>
;; Keywords: wp, ebnf, PostScript
;; Old-Version: 1.0
;; Package: ebnf2ps

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;
;; This is part of ebnf2ps package.
;;
;; This package defines an optimizer for ebnf2ps.
;;
;; See ebnf2ps.el for documentation.
;;
;;
;; Optimizations
;; -------------
;;
;;
;; *To be implemented*:
;;    left recursion:
;;    A = B | A C B | A C D.   ==>   A = B {C (B | D)}*.
;;
;;    right recursion:
;;    A = B | C A.             ==>   A = {C}* B.
;;    A = B | D | C A | E A.   ==>   A = { C | E }* ( B | D ).
;;
;;    optional:
;;    A = B | C B.             ==>   A = [C] B.
;;    A = B | B C.             ==>   A = B [C].
;;    A = D | B D | B C D.     ==>   A = [B [C]] D.
;;
;;
;; *Already implemented*:
;;    left recursion:
;;    A = B | A C.             ==>   A = B {C}*.
;;    A = B | A B.             ==>   A = {B}+.
;;    A =   | A B.             ==>   A = {B}*.
;;    A = B | A C B.           ==>   A = {B || C}+.
;;    A = B | D | A C | A E.   ==>   A = ( B | D ) { C | E }*.
;;
;;    optional:
;;    A = B | .                ==>   A = [B].
;;    A =   | B .              ==>   A = [B].
;;
;;    factorization:
;;    A = B C | B D.           ==>   A = B (C | D).
;;    A = C B | D B.           ==>   A = (C | D) B.
;;    A = B C E | B D E.       ==>   A = B (C | D) E.
;;
;;    none:
;;    A = B | C | .            ==>   A = B | C | .
;;    A = B | C A D.           ==>   A = B | C A D.
;;
;;
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; Code:


(require 'ebnf2ps)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(defvar ebnf-empty-rule-list nil
  "List of empty rule name.")


(defun ebnf-add-empty-rule-list (rule)
  "Add empty RULE in `ebnf-empty-rule-list'."
  (and ebnf-ignore-empty-rule
       (eq (ebnf-node-kind (ebnf-node-production rule))
	   'ebnf-generate-empty)
       (setq ebnf-empty-rule-list (cons (ebnf-node-name rule)
					ebnf-empty-rule-list))))


(defun ebnf-otz-initialize ()
  "Initialize optimizer."
  (setq ebnf-empty-rule-list nil))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Eliminate empty rules


(defun ebnf-eliminate-empty-rules (syntax-list)
  "Eliminate empty rules."
  (while ebnf-empty-rule-list
    (let ((ebnf-total (length syntax-list))
	  (ebnf-nprod 0)
	  (prod-list syntax-list)
	  new-list before)
      (while prod-list
	(ebnf-message-info "Eliminating empty rules")
	(let ((rule (car prod-list)))
	  ;; if any non-terminal pertains to ebnf-empty-rule-list
	  ;; then eliminate non-terminal from rule
	  (if (ebnf-eliminate-empty rule)
	      (setq before prod-list)
	    ;; eliminate empty rule from syntax-list
	    (setq new-list (cons (ebnf-node-name rule) new-list))
	    (if before
		(setcdr before (cdr prod-list))
	      (setq syntax-list (cdr syntax-list)))))
	(setq prod-list (cdr prod-list)))
      (setq ebnf-empty-rule-list new-list)))
  syntax-list)


;; [production   width-func entry height width name production action]
;; [sequence     width-func entry height width list]
;; [alternative  width-func entry height width list]
;; [non-terminal width-func entry height width name default]
;; [empty        width-func entry height width]
;; [terminal     width-func entry height width name default]
;; [special      width-func entry height width name default]

(defun ebnf-eliminate-empty (rule)
  (let ((kind (ebnf-node-kind rule)))
    (cond
     ;; non-terminal
     ((eq kind 'ebnf-generate-non-terminal)
      (if (member (ebnf-node-name rule) ebnf-empty-rule-list)
	  nil
	rule))
     ;; sequence
     ((eq kind 'ebnf-generate-sequence)
      (let ((seq    (ebnf-node-list rule))
	    (header (ebnf-node-list rule))
	    before elt)
	(while seq
	  (setq elt (car seq))
	  (if (ebnf-eliminate-empty elt)
	      (setq before seq)
	    (if before
		(setcdr before (cdr seq))
	      (setq header (cdr header))))
	  (setq seq (cdr seq)))
	(when header
	  (ebnf-node-list rule header)
	  rule)))
     ;; alternative
     ((eq kind 'ebnf-generate-alternative)
      (let ((seq    (ebnf-node-list rule))
	    (header (ebnf-node-list rule))
	    before elt)
	(while seq
	  (setq elt (car seq))
	  (if (ebnf-eliminate-empty elt)
	      (setq before seq)
	    (if before
		(setcdr before (cdr seq))
	      (setq header (cdr header))))
	  (setq seq (cdr seq)))
	(when header
	  (if (= (length header) 1)
	      (car header)
	    (ebnf-node-list rule header)
	    rule))))
     ;; production
     ((eq kind 'ebnf-generate-production)
      (let ((prod (ebnf-eliminate-empty (ebnf-node-production rule))))
	(when prod
	  (ebnf-node-production rule prod)
	  rule)))
     ;; terminal, special and empty
     (t
      rule)
     )))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Optimizations


;; *To be implemented*:
;;    left recursion:
;;    A = B | A C B | A C D.   ==>   A = B {C (B | D)}*.

;;    right recursion:
;;    A = B | C A.             ==>   A = {C}* B.
;;    A = B | D | C A | E A.   ==>   A = { C | E }* ( B | D ).

;;    optional:
;;    A = B | C B.             ==>   A = [C] B.
;;    A = B | B C.             ==>   A = B [C].
;;    A = D | B D | B C D.     ==>   A = [B [C]] D.


;; *Already implemented*:
;;    left recursion:
;;    A = B | A C.             ==>   A = B {C}*.
;;    A = B | A B.             ==>   A = {B}+.
;;    A =   | A B.             ==>   A = {B}*.
;;    A = B | A C B.           ==>   A = {B || C}+.
;;    A = B | D | A C | A E.   ==>   A = ( B | D ) { C | E }*.

;;    optional:
;;    A = B | .                ==>   A = [B].
;;    A =   | B .              ==>   A = [B].

;;    factorization:
;;    A = B C | B D.           ==>   A = B (C | D).
;;    A = C B | D B.           ==>   A = (C | D) B.
;;    A = B C E | B D E.       ==>   A = B (C | D) E.

;;    none:
;;    A = B | C | .            ==>   A = B | C | .
;;    A = B | C A D.           ==>   A = B | C A D.

(defun ebnf-optimize (syntax-list)
  "Syntactic chart optimizer."
  (if (not ebnf-optimize)
      syntax-list
    (let ((ebnf-total (length syntax-list))
	  (ebnf-nprod 0)
	  new)
      (while syntax-list
	(setq new (cons (ebnf-optimize1 (car syntax-list)) new)
	      syntax-list (cdr syntax-list)))
      (nreverse new))))


;; left recursion:
;; 1.  A = B | A C.             ==>   A = B {C}*.
;; 2.  A = B | A B.             ==>   A = {B}+.
;; 3.  A =   | A B.             ==>   A = {B}*.
;; 4.  A = B | A C B.           ==>   A = {B || C}+.
;; 5.  A = B | D | A C | A E.   ==>   A = ( B | D ) { C | E }*.

;; optional:
;; 6.  A = B | .                ==>   A = [B].
;; 7.  A =   | B .              ==>   A = [B].

;; factorization:
;; 8.  A = B C | B D.           ==>   A = B (C | D).
;; 9.  A = C B | D B.           ==>   A = (C | D) B.
;; 10. A = B C E | B D E.       ==>   A = B (C | D) E.

(defun ebnf-optimize1 (prod)
  (ebnf-message-info "Optimizing syntactic chart")
  (let ((production (ebnf-node-production prod)))
    (and (eq (ebnf-node-kind production) 'ebnf-generate-alternative)
	 (let* ((hlist (ebnf-split-header-prefix
			(ebnf-node-list production)
			(ebnf-node-name prod)))
		(nlist (car hlist))
		(zlist (cdr hlist))
		(elist (ebnf-split-header-suffix nlist zlist)))
	   (ebnf-node-production
	    prod
	    (cond
	     ;; cases 2., 4.
	     (elist
	      (and (eq elist t)
		   (setq elist nil))
	      (setq elist (or (ebnf-prefix-suffix elist)
			      elist))
	      (let* ((nl (ebnf-extract-empty nlist))
		     (el (or (ebnf-prefix-suffix (cdr nl))
			     (ebnf-create-alternative (cdr nl)))))
		(if (car nl)
		    (ebnf-make-zero-or-more el elist)
		  (ebnf-make-one-or-more el elist))))
	     ;; cases 1., 3., 5.
	     (zlist
	      (let* ((xlist (cdr (ebnf-extract-empty zlist)))
		     (znode (ebnf-make-zero-or-more
			     (or (ebnf-prefix-suffix xlist)
				 (ebnf-create-alternative xlist))))
		     (nnode (ebnf-map-list-to-optional nlist)))
		(and nnode
		     (setq nlist (list nnode)))
		(if (or (null nlist)
			(and (= (length nlist) 1)
			     (eq (ebnf-node-kind (car nlist))
				 'ebnf-generate-empty)))
		    znode
		  (ebnf-make-sequence
		   (list (or (ebnf-prefix-suffix nlist)
			     (ebnf-create-alternative nlist))
			 znode)))))
	     ;; cases 6., 7.
	     ((ebnf-map-node-to-optional production)
	      )
	     ;; cases 8., 9., 10.
	     ((ebnf-prefix-suffix nlist)
	      )
	     ;; none
	     (t
	      production)
	     ))))
    prod))


(defun ebnf-split-header-prefix (node-list header)
  (let* ((hlist (ebnf-split-header-prefix1 node-list header))
	 (nlist (car hlist))
	 zlist empty-p)
    (while (setq hlist (cdr hlist))
      (let ((elt (car hlist)))
	(if (eq (ebnf-node-kind elt) 'ebnf-generate-sequence)
	    (setq zlist (cons
			 (let ((seq (cdr (ebnf-node-list elt))))
			   (if (= (length seq) 1)
			       (car seq)
			     (ebnf-node-list elt seq)
			     elt))
			 zlist))
	  (setq empty-p t))))
    (and empty-p
	 (setq zlist (cons (ebnf-make-empty)
			   zlist)))
    (cons nlist (nreverse zlist))))


(defun ebnf-split-header-prefix1 (node-list header)
  (let (hlist nlist)
    (while node-list
      (if (ebnf-node-equal-header (car node-list) header)
	  (setq hlist (cons (car node-list) hlist))
	(setq nlist (cons (car node-list) nlist)))
      (setq node-list (cdr node-list)))
    (cons (nreverse nlist) (nreverse hlist))))


(defun ebnf-node-equal-header (node header)
  (let ((kind (ebnf-node-kind node)))
    (cond
     ((eq kind 'ebnf-generate-sequence)
      (ebnf-node-equal-header (car (ebnf-node-list node)) header))
     ((eq kind 'ebnf-generate-non-terminal)
      (string= (ebnf-node-name node) header))
     (t
      nil)
     )))


(defun ebnf-map-node-to-optional (node)
  (and (eq (ebnf-node-kind node) 'ebnf-generate-alternative)
       (ebnf-map-list-to-optional (ebnf-node-list node))))


(defun ebnf-map-list-to-optional (nlist)
  (and (= (length nlist) 2)
       (let ((first  (nth 0 nlist))
	     (second (nth 1 nlist)))
	 (cond
	  ;; empty second
	  ((eq (ebnf-node-kind first) 'ebnf-generate-empty)
	   (ebnf-make-optional second))
	  ;; first empty
	  ((eq (ebnf-node-kind second) 'ebnf-generate-empty)
	   (ebnf-make-optional first))
	  ;; first second
	  (t
	   nil)
	  ))))


(defun ebnf-extract-empty (elist)
  (let ((now elist)
	before empty-p)
    (while now
      (if (not (eq (ebnf-node-kind (car now)) 'ebnf-generate-empty))
	  (setq before now)
	(setq empty-p t)
	(if before
	    (setcdr before (cdr now))
	  (setq elist (cdr elist))))
      (setq now (cdr now)))
    (cons empty-p elist)))


(defun ebnf-split-header-suffix (nlist zlist)
  (let (new empty-p)
    (and (cond
	  ((= (length nlist) 1)
	   (let ((ok t)
		 (elt (car nlist)))
	     (while (and ok zlist)
	       (setq ok    (ebnf-split-header-suffix1 elt (car zlist))
		     zlist (cdr zlist))
	       (if (eq ok t)
		   (setq empty-p t)
		 (setq new (cons ok new))))
	     ok))
	  ((= (length nlist) (length zlist))
	   (let ((ok t))
	     (while (and ok zlist)
	       (setq ok    (ebnf-split-header-suffix1 (car nlist) (car zlist))
		     nlist (cdr nlist)
		     zlist (cdr zlist))
	       (if (eq ok t)
		   (setq empty-p t)
		 (setq new (cons ok new))))
	     ok))
	  (t
	   nil)
	  )
	 (let* ((lis (ebnf-unique-list new))
		(len (length lis)))
	   (cond
	    ((zerop len)
	     t)
	    ((= len 1)
	     (setq lis (car lis))
	     (if empty-p
		 (ebnf-make-optional lis)
	       lis))
	    (t
	     (and empty-p
		  (setq lis (cons (ebnf-make-empty) lis)))
	     (ebnf-create-alternative (nreverse lis)))
	    )))))


(defun ebnf-split-header-suffix1 (ne ze)
  (cond
   ((eq (ebnf-node-kind ne) 'ebnf-generate-sequence)
    (and (eq (ebnf-node-kind ze) 'ebnf-generate-sequence)
	 (let ((nl (ebnf-node-list ne))
	       (zl (ebnf-node-list ze))
	       len z)
	   (and (>= (length zl) (length nl))
		(let ((ok t))
		  (setq len (- (length zl) (length nl))
			z   (nthcdr len zl))
		  (while (and ok z)
		    (setq ok (ebnf-node-equal (car z) (car nl))
			  z  (cdr z)
			  nl (cdr nl)))
		  ok)
		(if (zerop len)
		    t
		  (setcdr (nthcdr (1- len) zl) nil)
		  ze)))))
   ((eq (ebnf-node-kind ze) 'ebnf-generate-sequence)
    (let* ((zl  (ebnf-node-list ze))
	   (len (length zl)))
      (and (ebnf-node-equal ne (car (nthcdr (1- len) zl)))
	   (cond
	    ((= len 1)
	     t)
	    ((= len 2)
	     (car zl))
	    (t
	     (setcdr (nthcdr (- len 2) zl) nil)
	     ze)
	    ))))
   (t
    (ebnf-node-equal ne ze))
   ))


(defun ebnf-prefix-suffix (lis)
  (and lis (listp lis)
       (let* ((prefix (ebnf-split-prefix lis))
	      (suffix (ebnf-split-suffix (cdr prefix)))
	      (middle (cdr suffix)))
	 (setq prefix (car prefix)
	       suffix (car suffix))
	 (and (or prefix suffix)
	      (ebnf-make-sequence
	       (nconc prefix
		      (and middle
			   (list (or (ebnf-map-list-to-optional middle)
				     (ebnf-create-alternative middle))))
		      suffix))))))


(defun ebnf-split-prefix (lis)
  (let* ((len  (length lis))
	 (tail lis)
	 (head (if (eq (ebnf-node-kind (car lis)) 'ebnf-generate-sequence)
		   (ebnf-node-list (car lis))
		 (list (car lis))))
	 (ipre (1+ len)))
    ;; determine prefix length
    (while (and (> ipre 0) (setq tail (cdr tail)))
      (let ((cur head)
	    (this (if (eq (ebnf-node-kind (car tail)) 'ebnf-generate-sequence)
		      (ebnf-node-list (car tail))
		    (list (car tail))))
	    (i 0))
	(while (and cur this
		    (ebnf-node-equal (car cur) (car this)))
	  (setq cur  (cdr cur)
		this (cdr this)
		i    (1+ i)))
	(setq ipre (min ipre i))))
    (if (or (zerop ipre) (> ipre len))
	;; no prefix at all
	(cons nil lis)
      (let* ((tail   (nthcdr ipre head))
	     ;; get prefix
	     (prefix (progn
		       (and tail
			    (setcdr (nthcdr (1- ipre) head) nil))
		       head))
	     empty-p before)
	;; adjust first element
	(if (or (not (eq (ebnf-node-kind (car lis)) 'ebnf-generate-sequence))
		(null tail))
	    (setq lis     (cdr lis)
		  tail    lis
		  empty-p t)
	  (if (= (length tail) 1)
	      (setcar lis (car tail))
	    (ebnf-node-list (car lis) tail))
	  (setq tail (cdr lis)))
	;; eliminate prefix from lis based on ipre
	(while tail
	  (let ((elt (car tail))
		rest)
	    (if (and (eq (ebnf-node-kind elt) 'ebnf-generate-sequence)
		     (setq rest (nthcdr ipre (ebnf-node-list elt))))
		(progn
		  (if (= (length rest) 1)
		      (setcar tail (car rest))
		    (ebnf-node-list elt rest))
		  (setq before tail))
	      (setq empty-p t)
	      (if before
		  (setcdr before (cdr tail))
		(setq lis (cdr lis))))
	    (setq tail (cdr tail))))
	(cons prefix (ebnf-unique-list
		      (if empty-p
			  (nconc lis (list (ebnf-make-empty)))
			lis)))))))


(defun ebnf-split-suffix (lis)
  (let* ((len  (length lis))
	 (tail lis)
	 (head (nreverse
		(if (eq (ebnf-node-kind (car lis)) 'ebnf-generate-sequence)
		    (ebnf-node-list (car lis))
		  (list (car lis)))))
	 (isuf (1+ len)))
    ;; determine suffix length
    (while (and (> isuf 0) (setq tail (cdr tail)))
      (let* ((cur head)
	     (tlis (nreverse
		    (if (eq (ebnf-node-kind (car tail)) 'ebnf-generate-sequence)
			(ebnf-node-list (car tail))
		      (list (car tail)))))
	     (this tlis)
	     (i 0))
	(while (and cur this
		    (ebnf-node-equal (car cur) (car this)))
	  (setq cur  (cdr cur)
		this (cdr this)
		i    (1+ i)))
	(nreverse tlis)
	(setq isuf (min isuf i))))
    (setq head (nreverse head))
    (if (or (zerop isuf) (> isuf len))
	;; no suffix at all
	(cons nil lis)
      (let* ((n      (- (length head) isuf))
	     ;; get suffix
	     (suffix (nthcdr n head))
	     (tail   (and (> n 0)
			  (progn
			    (setcdr (nthcdr (1- n) head) nil)
			    head)))
	     before empty-p)
	;; adjust first element
	(if (or (not (eq (ebnf-node-kind (car lis)) 'ebnf-generate-sequence))
		(null tail))
	    (setq lis     (cdr lis)
		  tail    lis
		  empty-p t)
	  (if (= (length tail) 1)
	      (setcar lis (car tail))
	    (ebnf-node-list (car lis) tail))
	  (setq tail (cdr lis)))
	;; eliminate suffix from lis based on isuf
	(while tail
	  (let ((elt (car tail))
		rest)
	    (if (and (eq (ebnf-node-kind elt) 'ebnf-generate-sequence)
		     (setq rest (ebnf-node-list elt)
			   n    (- (length rest) isuf))
		     (> n 0))
		(progn
		  (if (= n 1)
		      (setcar tail (car rest))
		    (setcdr (nthcdr (1- n) rest) nil)
		    (ebnf-node-list elt rest))
		  (setq before tail))
	      (setq empty-p t)
	      (if before
		  (setcdr before (cdr tail))
		(setq lis (cdr lis))))
	    (setq tail (cdr tail))))
	(cons suffix (ebnf-unique-list
		      (if empty-p
			  (nconc lis (list (ebnf-make-empty)))
			lis)))))))


(defun ebnf-unique-list (nlist)
  (let ((current nlist)
	before)
    (while current
      (let ((tail (cdr current))
	    (head (car current))
	    remove-p)
	(while tail
	  (if (not (ebnf-node-equal head (car tail)))
	      (setq tail (cdr tail))
	    (setq remove-p t
		  tail     nil)
	    (if before
		(setcdr before (cdr current))
	      (setq nlist (cdr nlist)))))
	(or remove-p
	    (setq before current))
	(setq current (cdr current))))
    nlist))


(defun ebnf-node-equal (A B)
  (let ((kindA (ebnf-node-kind A))
	(kindB (ebnf-node-kind B)))
    (and (eq kindA kindB)
	 (cond
	  ;; empty
	  ((eq kindA 'ebnf-generate-empty)
	   t)
	  ;; non-terminal, terminal, special
	  ((memq kindA '(ebnf-generate-non-terminal
			 ebnf-generate-terminal
			 ebnf-generate-special))
	   (string= (ebnf-node-name A) (ebnf-node-name B)))
	  ;; alternative, sequence
	  ((memq kindA '(ebnf-generate-alternative ; any order
			 ebnf-generate-sequence)) ; order is important
	   (let ((listA (ebnf-node-list A))
		 (listB (ebnf-node-list B)))
	     (and (= (length listA) (length listB))
		  (let ((ok t))
		    (while (and ok listA)
		      (setq ok    (ebnf-node-equal (car listA) (car listB))
			    listA (cdr listA)
			    listB (cdr listB)))
		    ok))))
	  ;; production
	  ((eq kindA 'ebnf-generate-production)
	   (and (string= (ebnf-node-name A) (ebnf-node-name B))
		(ebnf-node-equal (ebnf-node-production A)
				 (ebnf-node-production B))))
	  ;; otherwise
	  (t
	   nil)
	  ))))


(defun ebnf-create-alternative (alt)
  (if (> (length alt) 1)
      (ebnf-make-alternative alt)
    (car alt)))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


(provide 'ebnf-otz)


;;; ebnf-otz.el ends here
