(uiop:define-package :st-buchberger/src/parser
(:nicknames :parser)
(:export #:polynomial #:term #:term-monomial #:*zero-polynomial*
#:term-coefficient #:monomial-indeterminates #:polynomial-terms
#:parse-polynomial #:parse-term #:parse-coefficient
#:parse-monomial #:parse-expt-symbol))
(in-package :st-buchberger/src/parser)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Grammar
;; =======
;;
;; polynomial : term
;; | "(" "-" term+ ")"
;; | "(" "+" term* ")"
;;
;; term : coefficient
;; | monomial
;; | "(" "*" coefficient monomial ")"
;; | "(" "-" term ")"
;;
;; coefficient : rational
;;
;; monomial : expt-symbol
;; | "(" expt-symbol+ ")"
;;
;; expt-symbol : symbol
;; | "(" "expt" symbol non-negative-integer ")"
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defstruct polynomial
terms)
(defstruct term
coefficient
monomial)
(defstruct monomial
"Association list whose keys are indeterminates (symbols) and the
corresponding values are the powers."
indeterminates)
(defparameter *zero-polynomial*
(make-polynomial :terms (list (make-term :coefficient 0
:monomial (make-monomial
:indeterminates nil))))
"Representation of the zero polynomial.")
(defun parse-polynomial (sexp)
"Parse polynomial in SEXP."
(cond
((null sexp)
(error "Invalid expression: ~S." sexp))
((and (consp sexp) (eq (first sexp) '+))
(if (zerop (length (rest sexp)))
*zero-polynomial*
(make-polynomial :terms (loop :for x :in (rest sexp)
:collect (parse-term x)))))
((and (consp sexp) (eq (first sexp) '-))
(case (length (rest sexp))
(0 *zero-polynomial*)
(1 (make-polynomial :terms (list (parse-term sexp))))
(t (make-polynomial :terms (loop :for x :in (rest sexp)
:for term := (parse-term x)
:for i :from 0
:when (plusp i) :do
(setf (term-coefficient term)
(- (term-coefficient term)))
:collect term)))))
(t (make-polynomial :terms (list (parse-term sexp))))))
(defun parse-term (sexp)
"Parse term in SEXP."
(cond
((atom sexp)
(if (numberp sexp)
(make-term :coefficient (parse-coefficient sexp)
:monomial (make-monomial :indeterminates nil))
(make-term :coefficient 1 :monomial (parse-monomial sexp))))
((consp sexp)
(destructuring-bind (first . rest) sexp
(case first
(* (let ((coeff (car rest)))
(if (numberp coeff)
(make-term :coefficient (parse-coefficient coeff)
:monomial (parse-monomial (cdr rest)))
(make-term :coefficient 1
:monomial (parse-monomial rest)))))
(- (let ((term (apply #'parse-term rest)))
(setf (term-coefficient term) (- (term-coefficient term)))
term))
(t (make-term :coefficient 1
:monomial (parse-monomial sexp))))))))
(defun parse-coefficient (sexp)
"Parse coefficient in SEXP."
(unless (and (numberp sexp) (typep sexp 'rational))
(error "Invalid coefficient: ~S." sexp))
sexp)
(defun parse-monomial (sexp)
"Parse monomial in SEXP."
(make-monomial
:indeterminates (if (atom sexp)
(list (parse-expt-symbol sexp))
(if (eq (car sexp) 'expt)
(list (parse-expt-symbol sexp))
(loop :for x :in sexp
:collect (parse-expt-symbol x))))))
(defun parse-expt-symbol (sexp)
"Parse (possibly exponentiated) symbol in SEXP."
(flet ((error-invalid-expt-symbol (sexp)
(error "Invalid (possibly exponentiated) symbol: ~S." sexp)))
(cond
((atom sexp)
(unless (symbolp sexp)
(error-invalid-expt-symbol sexp))
(cons sexp 1))
(t
(destructuring-bind (op symbol power) sexp
(unless (and (eq op 'expt)
(symbolp symbol)
(typep power '(integer 0)))
(error-invalid-expt-symbol sexp))
(cons symbol power))))))