;;;; -*- mode:emacs-lisp;coding:utf-8 -*-
;;;;******************************************************************************
;;;;FILE:               pjb-i2p-expression.el
;;;;LANGUAGE:           emacs lisp
;;;;SYSTEM:             emacs
;;;;USER-INTERFACE:     emacs
;;;;DESCRIPTION
;;;;
;;;;    This packages exports functions to convert infix expressions
;;;;    to prefix s-expressions,
;;;;    and to simplify and evaluate these s-expressions.
;;;;
;;;;    i2p-calculette, i2p-evaluate, i2p-eval, i2p-simplify, i2p-expression.
;;;;
;;;;    SEE ALSO: pjb-expression.el which implement a calculette, evaluate and
;;;;              parse from a string instead of from a parsed i-expr.
;;;;
;;;;AUTHORS
;;;;    <PJB> Pascal J. Bourguignon
;;;;MODIFICATIONS
;;;;    2002-12-27 <PJB> Creation.
;;;;BUGS
;;;;LEGAL
;;;;    LGPL
;;;;
;;;;    Copyright Pascal J. Bourguignon 2002 - 2011
;;;;
;;;;    This library is free software; you can redistribute it and/or
;;;;    modify it under the terms of the GNU Lesser General Public
;;;;    License as published by the Free Software Foundation; either
;;;;    version 2 of the License, or (at your option) any later version.
;;;;
;;;;    This library 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
;;;;    Lesser General Public License for more details.
;;;;
;;;;    You should have received a copy of the GNU Lesser General Public
;;;;    License along with this library; if not, write to the Free Software
;;;;    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
;;;;
;;;;******************************************************************************
(require 'pjb-cl)
(provide 'pjb-i2p-expression)


;;----------------------------------------------------------------------
;;   expression     ::= numexpression | boolcomparison .
;;
;;   disjonction    ::= conjonction [ or disjonction ]
;;   conjonction    ::= proposition [ and conjonction ]
;;   proposition    ::= 'not' proposition | numcomparison | boolsimple
;;   boolsimple     ::= fun-name ( expression { , expression } )
;;                    | boolconstant | variable | (disjonction )
;;   numcomparison  ::= numexpression [ numcomp-op numexpression ]
;;   boolcomparison ::= boolexpression [ boolcom-op boolexpression ]
;;
;;   numexpression  ::= numexpression [ termop term ]
;;   term           ::= term [ factop factor ]
;;   factor         ::= factor [ infix-op infix ]
;;   infix          ::= prefix-op infix | suffix
;;   suffix         ::= suffix suffix-op | simple
;;   simple         ::= fun-name ( expression { , expression } )
;;                    | numconstant | variable | ( numexpression )
;;
;;   fun-name must be a fbound symbol.
;;   variable must be bound or have the property :operator :unknown
;;   constants are strings, numbers or arrays
;;   (numerical operators could be defined for arrays too)
;;
;;   plist:
;;     :operator  :term
;;     :operator  :factor
;;     :operator  :infix
;;     :operator  :suffix
;;     :operator  :prefix
;;     :operator  :function
;;     :operator  :numcomp
;;     :operator  :boolcomp
;;
;;   termop         ::= + | - | ...
;;   factop         ::= * | / | ...
;;   prefix-op      ::= - | ...
;;   suffix-op      ::= ! | ...
;;   infix-op       ::= ^ | ...
;;   numcomp-op     ::= < | <= | = | /= | > | >=
;;   boolcomp-op    ::= <=> | <== | ==> | xor
;;----------------------------------------------------------------------


(eval-when (compile load eval)
  (put 'i2p-error 'error-conditions '(i2p-error error)))


(defun i2p-calculette-to-lisp ()
  "See i2p-calculette."
  (interactive)
  (i2p-calculette t))

(defun i2p-calculette (&optional displayLisp)
  (interactive "P")
  (let* ((from-point  (progn (beginning-of-line) (point)))
         (last-point  (progn (end-of-line)       (point)))
         (source  (concat "( " (buffer-substring from-point last-point) " )"))
         i-expr pos)
    (let ((rfs (read-from-string source)))
      (setq i-expr (car rfs) pos (cdr rfs)))
    (if (/= pos (length source))
        (signal 'scan-error (list "Unbalanced parentheses"
                                  (- (+ from-point pos) 2))))
    (goto-char last-point)
    (insert (i2p-evaluate i-expr displayLisp))))



(defun i2p-evaluate (i-expr &optional displayLisp)
  (condition-case error
      (multiple-value-bind (s-expr rest) (i2p-expression i-expr)
        (if rest
            (signal 'i2p-error (list (format "Remaining %S" rest) rest)))
        (setq s-expr (i2p-simplify s-expr))
        (concatenate 'string
          (if displayLisp (format "\n%S" s-expr) "")
          (multiple-value-bind (res ue) (i2p-eval s-expr)
            (format "\n %S\n" res))))))



(defun i2p-eval (s-expr)
  "
DO:      evaluates as much as possible of s-expr.
RETURN:  a value or a partially evalued s-expr,
         whether s-expr is completely evalued
"
  (if (consp s-expr)
      (multiple-value-bind (arguments all-evaluated)
          (do ((ue-args (cdr s-expr) (cdr ue-args))
               (e-args)
               (e-all t))
              ((null ue-args) (values (nreverse e-args) e-all))
            (multiple-value-bind (arg evaluated) (i2p-eval (car ue-args))
              (push arg e-args)
              (unless evaluated (setq e-all nil))))
        (if all-evaluated
            (condition-case error
                (values (eval (cons (car s-expr) arguments)) t)
              (error (values (cons (car s-expr) arguments) nil)))
          (values (cons (car s-expr) arguments) nil)))
    (condition-case error
        (values (eval s-expr) t)
      (error (values s-expr nil)))))




(defun more-than-one-element (list)
  "
RETURN: (< 1 (length list))
"
  (and (consp list)
       (consp (cdr list))))



;;; (show (more-than-one-element nil)
;;;       (more-than-one-element :not-a-list)
;;;       (more-than-one-element '(a))
;;;       (more-than-one-element '(a b))
;;;       (more-than-one-element '(a b c)) )


(defun i2p-simplify (sexp)
  "
RETURN: A simplified sexp.
NOTE:   Implemented rules:
        - collapsing { < = >, and, or, +, *, neg }
        - left-collapsing { -, / }
        - neg(neg(a)) = a
        - neg(0) = 0
        - a+0 = 0+a = a
        - a-0 = a
        - a-a = 0
        - a*0 = 0*a = 0
        - a*1 = 1*a = a
        - t or a  =  a or t  =  t
        - nil and a  =  a and nil  =  nil
        - t and t  =  t
        - nil or nil  =  nil
        - t xor t  =  nil xor nil  =  nil
        - t xor nil  =  nil xor t  =  t
        Not implemented yet:
        - 0-a = -a
        - a/1 = a
        - a^1 = a
        - a^0 = 0 if a/ = 0
        - 0^b = 0 if b/ = 0
        Could be implemented too:
        - a-b-c = a-(b+c)
        - a/b/c = a/(b*c)
"
  ;;  (setq sexp '(neg (neg 2)) operator (car sexp) subsexps (cdr sexp))
  (block :simplifying
    (if (consp sexp)
        (let ((operator (car sexp))
              (subsexps (cdr sexp)))
          ;; phase 0: simplifying subexpressions
          (setq subsexps (mapcar (function i2p-simplify) subsexps))
          ;; phase 1: collapsing
          (case operator
            ((<=> and or + *)
             (setq subsexps
                   (mapcan (lambda (sub)
                             (if (consp sub)
                                 (if (eq operator (car sub))
                                     (cdr sub) (list sub)) (list sub)))
                           subsexps)) )
            ((- /)
             (setq subsexps
                   (if (and (consp (car subsexps))
                            (eq operator (caar subsexps)))
                       (append (cdar subsexps) (cdr subsexps))
                     subsexps)) )
            (neg
             (when (and (consp (car subsexps))
                        (eq operator (caar subsexps)))
               (setq sexp (cadar subsexps))
               (return-from :simplifying)) )
            ) ;;case
          ;; phase 2: neutral or absorbing elements
          (case operator
            (+
             (setq subsexps (mapcan (lambda (sub)
                                      (if (and (numberp sub) (= 0 sub))
                                          nil (list sub)))
                                    subsexps))
             (setq sexp (if subsexps
                            (if (more-than-one-element subsexps)
                                (cons operator subsexps)
                              (car subsexps)) 0)) )
            (-
             (setq subsexps (cons (car subsexps)
                                  (mapcan (lambda (sub)
                                            (if (and (numberp sub) (= 0 sub))
                                                nil (list sub)))
                                          (cdr subsexps))))
             (setq sexp (if (more-than-one-element subsexps)
                            (if (more-than-one-element (cdr subsexps))
                                (cons operator subsexps)
                              ;; TODO: (not (equal 0 0.0))
                              (if (equal (car subsexps) (cadr subsexps))
                                  0 (cons operator subsexps)))
                          (car subsexps) )) )
            (neg
             (if (and (numberp (car subsexps)) (= 0 (car subsexps)))
                 (setq sexp 0)))
            (*
             (if (find 0 subsexps :test (lambda (a b) (and (numberp a)
                                                           (numberp b)
                                                           (= a b))))
                 (setq sexp 0)
               (progn
                 (setq subsexps (mapcan (lambda (sub)
                                          (if (and (numberp sub) (= 1 sub))
                                              nil (list sub)))
                                        subsexps))
                 (setq sexp (if subsexps
                                (if (more-than-one-element subsexps)
                                    (cons operator subsexps)
                                  (car subsexps)) 1)))) )
            (/
             (setq subsexps (cons (car subsexps)
                                  (mapcan (lambda (sub)
                                            (if (and (numberp sub) (= 1 sub))
                                                nil (list sub)))
                                          (cdr subsexps))))
             (setq sexp (if (more-than-one-element subsexps)
                            (cons operator subsexps) (car subsexps))) )
            (=
             ;; TODO: (not (equal 0 0.0))
             (setq sexp
                   (if (and (= 2 (length subsexps))
                            (equal (car subsexps) (cadr subsexps)))
                       t
                     (cons operator subsexps))) )
            (and
             (setq sexp
                   (cond
                    ((every (lambda (x) (eq x t)) subsexps)   t)
                    ((null (cdr subsexps))                    (car subsexps))
                    ((position nil subsexps
                               :test (lambda (a b) (eq (not a) (not b))))  nil)
                    (t (cons operator subsexps)))) )
            (or
             (setq sexp
                   (cond
                    ((null subsexps)                           nil)
                    ((some (lambda (x) (eq x t)) subsexps)     t)
                    ((null (cdr subsexps))                     (car subsexps))
                    ((position t subsexps :test (function eq)) t)
                    (t (cons operator subsexps)))) )
            (xor
             (setq sexp
                   (let ((a (nth 0 subsexps))
                         (b (nth 1 subsexps)))
                     (cond
                      ((or (and (eq t   a) (eq t   b))
                           (and (eq nil a) (eq nil b))) nil)
                      ((or (and (eq nil a) (eq t   b))
                           (and (eq t   a) (eq nil b))) t)
                      (t (cons operator subsexps))))) )
            (t (setq sexp (cons operator subsexps)))
            ) ;;case
          ))) ;;symplifying
  sexp)



(defmacro <=> (&optional first-bool &rest other-bools)
  "
RETURN:  Whether all arguments are nil or all are not nil,
         but evaluating them  only as necessary
         (stops ealuating them as soon as one is not equivalent to the first).
"
  `(do ((first (not (eval ,first-bool)))
        (rest  ',other-bools (cdr rest)))
       ((or (null rest)
            (not (eq first (not (eval (car rest)))))) (null rest))))



;;;   (mapcar (lambda (e) (printf "%3s   ~S\n" (eval e) e))
;;;           '((<=>)
;;;             (<=> nil) (<=> t)
;;;             (<=> nil nil) (<=> nil t) (<=> t nil)  (<=> t t)
;;;             (<=> nil nil nil) (<=> nil t nil) (<=> t nil nil)  (<=> t t nil)
;;;             (<=> nil nil t) (<=> nil t t) (<=> t nil t)  (<=> t t t)))
;;; (show (macroexpand (quote (<=> nil nil t))))


(defun ==> (p q) "RETURN:  p ==> q" (or (not p) q))
(defun <== (q p) "RETURN:  q <=> p" (or (not p) q))
(defun xor (p q) "RETURN:  p xor q" (not (eq (not p) (not q))))
(defun fact (n) (if (< n 2) 1 (* n (fact (1- n)))))
(defalias '! 'fact)
(defalias 'neg '-)

;;; (show (xor nil nil) (xor nil t) (xor t nil) (xor t t) (xor 1 2) (xor nil 2))


(defmacro i2p-try-both (sexp-1 sexp-2)
  `(let ((pexp1 nil) (ires1 nil) (erro1 nil) (rlen1 most-positive-fixnum)
         (pexp2 nil) (ires2 nil) (erro2 nil) (rlen2 most-positive-fixnum))
     ;; try both
     (condition-case error
         (multiple-value-bind (s-expr i-rest) ,sexp-1
           (setq pexp1 s-expr ires1 i-rest rlen1 (length i-rest)))
       (i2p-error (setq erro1 (car error) rlen1 (length (cadr error))))
       (error (setq erro1 error)) )
     (if (or erro1 ires1) ;; if sexp-1 ate all without error,
         ;; then there's no need to try the other
         (progn
           (condition-case error
               (multiple-value-bind (s-expr i-rest) ,sexp-2
                 (setq pexp2 s-expr ires2 i-rest rlen2 (length i-rest)))
             (i2p-error (setq erro2 (car error) rlen2 (length (cadr error))))
             (error (setq erro2 error)) )
;;;      (mapc (lambda (s)
;;;              (show s (eval s)))
;;;            '(pexp1 ires1 erro1 rlen1 pexp2 ires2  erro2 rlen2))
           (if erro1
               (cond
                ((not erro2)     (values pexp2 ires2))
                ((< rlen1 rlen2) (signal (car erro1) (cdr erro1)))
                (t               (signal (car erro2) (cdr erro2))))
             (cond
              (erro2             (values pexp1 ires1))
              ((< rlen1 rlen2)   (values pexp1 ires1))
              (t                 (values pexp2 ires2)))))
       (values pexp1 ires1))))



(defun i2p-expression (expression)
  (i2p-try-both (i2p-numexpression expression)
                (i2p-boolcomparison expression) ))



(defun i2p-disjonction (disjonction)
  (multiple-value-bind (s-conjonction i-rest) (i2p-conjonction disjonction)
    (cond
     ((null i-rest)               (values s-conjonction i-rest))
     ((not (eq 'or (car i-rest))) (values s-conjonction i-rest))
     ((< (length i-rest) 2)
      (signal :i2p-error
              (list (format "Missing disjonction after %S" (car i-rest))
                    i-rest)) )
     (t (multiple-value-bind (s-disjonction ii-rest)
            (i2p-disjonction (cdr i-rest))
          (values (list (car i-rest) s-conjonction s-disjonction) ii-rest))))))



(defun i2p-conjonction (conjonction)
  (multiple-value-bind (s-proposition i-rest) (i2p-proposition conjonction)
    (cond
     ((null i-rest)                (values s-proposition i-rest))
     ((not (eq 'and (car i-rest))) (values s-proposition i-rest))
     ((< (length i-rest) 2)
      (signal :i2p-error
              (list (format "Missing conjonction after %S" (car i-rest))
                    i-rest)) )
     (t (multiple-value-bind (s-conjonction ii-rest)
            (i2p-conjonction (cdr i-rest))
          (values (list (car i-rest) s-proposition s-conjonction) ii-rest))))))



(defun i2p-proposition (proposition)
  (if (eq 'not (car proposition))
      (multiple-value-bind (s-proposition i-rest)
          (i2p-proposition (cdr proposition))
        (values (list (car proposition) s-proposition) i-rest))

    (i2p-try-both (i2p-numcomparison proposition)
                  (i2p-boolsimple proposition) )))





(defun i2p-boolcomparison (comparison)
  (multiple-value-bind (s-expr-1 i-rest) (i2p-disjonction comparison)
    (if (and i-rest (i2p-boolcompop-p (car i-rest)))
        (multiple-value-bind (s-expr-2 ii-rest) (i2p-disjonction (cdr i-rest))
          (values (list (car i-rest) s-expr-1 s-expr-2) ii-rest))
      (values s-expr-1 i-rest))))



(defun i2p-numcomparison (comparison)
  (multiple-value-bind (s-expr-1 i-rest) (i2p-numexpression comparison)
    (if (and i-rest (i2p-numcompop-p (car i-rest)))
        (multiple-value-bind (s-expr-2 ii-rest) (i2p-numexpression (cdr i-rest))
          (values (list (car i-rest) s-expr-1 s-expr-2) ii-rest))
      (values s-expr-1 i-rest))))



(defun i2p-boolsimple (simple)
  (let ((first (car simple)))
    (cond
     ((consp first)
      (multiple-value-bind (i-expr i-rest) (i2p-disjonction first)
        (if i-rest
            (signal :i2p-error
                    (list (format "Unexpected rest: %S" i-rest) i-rest)) )
        (values i-expr (cdr simple))))
     ((i2p-function-p first)
      ;;(and (not (i2p-anyop-p first)) (consp (cadr simple))))
      (values (cons first (i2p-argument-list (cadr simple))) (cddr simple)) )
     ((i2p-anyop-p first)
      (signal :i2p-error
              (list (format  "Syntax error from %S" simple) simple)) )
     (t
      (values first (cdr simple))))))



(defun i2p-numexpression (expression)
  (multiple-value-bind (s-term i-rest) (i2p-term expression)
    (cond
     ((null i-rest)                        (values s-term i-rest))
     ((not (i2p-termop-p (car i-rest)))    (values s-term i-rest))
     ((< (length i-rest) 2)
      (signal :i2p-error
              (list (format "Missing expression after %S" (car i-rest))
                    i-rest)) )
     (t (multiple-value-bind (s-expr ii-rest) (i2p-numexpression (cdr i-rest))
          (values (list (car i-rest) s-term s-expr) ii-rest))))))



(defun i2p-term (term)
  (multiple-value-bind (s-factor i-rest) (i2p-factor term)
    (cond
     ((null i-rest)                        (values s-factor i-rest))
     ((not (i2p-factorop-p (car i-rest)))  (values s-factor i-rest))
     ((< (length i-rest) 2)
      (signal :i2p-error
              (list (format "Missing term after %S" (car i-rest))
                    i-rest)) )
     (t (multiple-value-bind (s-term ii-rest) (i2p-term (cdr i-rest))
          (values (list (car i-rest) s-factor s-term) ii-rest))))))



(defun i2p-factor (factor)
  (multiple-value-bind (s-infix i-rest) (i2p-infix factor)
    (cond
     ((null i-rest)                        (values s-infix i-rest))
     ((not (i2p-infixop-p (car i-rest)))   (values s-infix i-rest))
     ((< (length i-rest) 2)
      (signal :i2p-error
              (list (format "Missing factor after %S" (car i-rest))
                    i-rest)) )
     (t (multiple-value-bind (s-factor ii-rest) (i2p-factor (cdr i-rest))
          (values (list (car i-rest) s-infix s-factor) ii-rest))))))



(defun i2p-infix (infix)
  (if (i2p-prefixop-p (car infix))
      (multiple-value-bind (s-infix i-rest) (i2p-infix (cdr infix))
        (values (list (if (eq '- (car infix)) 'neg (car infix))
                      s-infix) i-rest))
    (i2p-suffix infix)))



(defun i2p-suffix (suffix)
  (multiple-value-bind (s-suffix i-rest) (i2p-simple suffix)
    (do ()
        ( (or (null i-rest) (not (i2p-suffixop-p (car i-rest))))
          (values s-suffix i-rest) )
      (setq s-suffix (list (car i-rest) s-suffix)
            i-rest   (cdr i-rest)))))



(defun i2p-argument-list (arguments)
  (do ((s-arguments nil)
       (rest arguments) )
      ((null rest) (nreverse s-arguments))
    (multiple-value-bind (s-arg i-rest) (i2p-expression rest)
      (push s-arg s-arguments)
      (if i-rest
          (if (or (eq '\, (car i-rest)) (eq ': (car i-rest)))
              (progn
                (if (null (cdr i-rest))
                    (signal :i2p-error
                            (list (format "Missing argument after %S"
                                          (car i-rest)) i-rest)))
                (setq rest (cdr i-rest)))
            (signal :i2p-error
                    (list (format "Expected a coma insteand of %S"
                                  (car i-rest)) i-rest)))
        (setq rest i-rest)))))



(defun i2p-simple (simple)
  (let ((first (car simple)))
    (cond
     ((consp first)
      (multiple-value-bind (i-expr i-rest) (i2p-numexpression first)
        (if i-rest
            (signal :i2p-error
                    (list (format "Unexpected rest: %S" i-rest) i-rest)) )
        (values i-expr (cdr simple))))
     ((i2p-function-p first)
      ;;    (and (not (i2p-anyop-p first)) (consp (cadr simple))))
      (values (cons first (i2p-argument-list (cadr simple))) (cddr simple)) )
     ((i2p-anyop-p first)
      (signal :i2p-error
              (list (format "Syntax error from %S" simple) simple)) )
     (t
      (values first (cdr simple))))))




(defun i2p-numcompop-p (operator)
  (or (member* operator '(< <= = /= >= >) :test (function eq))
      (and (symbolp operator) (eq (get operator :operator) :numcomp))))



(defun i2p-boolcompop-p (operator)
  (or (member* operator '(<== <=> xor ==>) :test (function eq))
      (and (symbolp operator) (eq (get operator :operator) :boolcomp))))



(defun i2p-termop-p (operator)
  (or (eq operator '+)
      (eq operator '-)
      (and (symbolp operator) (eq (get operator :operator) :term))))



(defun i2p-factorop-p (operator)
  (or (eq operator '*)
      (eq operator '/)
      (and (symbolp operator) (eq (get operator :operator) :factor))))



(defun i2p-prefixop-p (operator)
  (or (eq operator '-)
      (and (symbolp operator) (eq (get operator :operator) :prefix))))



(defun i2p-infixop-p (operator)
  (or (eq operator '^)
      (and (symbolp operator) (eq (get operator :operator) :infix))))



(defun i2p-suffixop-p (operator)
  (or (eq operator '!)
      (and (symbolp operator) (eq (get operator :operator) :suffix))))



(defun i2p-function-p (operator)
  (and (symbolp operator)
       (or (fboundp operator)
           (eq (get operator :operator) :function))
       (not (or
             (i2p-termop-p operator)
             (i2p-factorop-p operator)
             (i2p-prefixop-p operator)
             (i2p-infixop-p operator)
             (i2p-suffixop-p operator)))))



(defun i2p-conjonctionop-p (operator)
  (eq operator 'and))


(defun i2p-disjonctionop-p (operator)
  (eq operator 'or))


(defun i2p-boolprefixop-p (operator)
  (eq operator 'not))


(defun i2p-anyop-p (operator)
  (find t '(
            i2p-numcompop-p i2p-boolcompop-p i2p-termop-p i2p-factorop-p
            i2p-prefixop-p i2p-infixop-p i2p-suffixop-p i2p-function-p
            i2p-conjonctionop-p i2p-disjonctionop-p i2p-boolprefixop-p )
        :test (function eq)
        :key  (lambda (x)
                (or (eq x t) (funcall x operator)) )))



;;;; pjb-i2p-expression.el            -- 2003-04-01 23:20:01 -- pascal   ;;;;
ViewGit