;;; Infix-to-Prefix --- Library for converting infix formula to Prefix expression
;;; Copyright © 2024 Masaya Tojo <masaya@tojo.tokyo>
;;;
;;; This file is part of Infix-to-Prefix.
;;;
;;; Infix-to-Prefix 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.
;;;
;;; Infix-to-Prefix 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 Infix-to-Prefix. If not, see
;;; <https://www.gnu.org/licenses/>.
(define-library (infix-to-prefix)
(export infix->prefix
prefix->infix
current-operator-rule-set)
(import (scheme base)
(scheme case-lambda)
(infix-to-prefix rule-set)
(only (srfi 1) car+cdr fold break! reverse! append! append-map! append-reverse!)
(only (srfi 26) cut cute))
(begin
(define (make-default-operator-rule-set)
(rule-set
(list
(operator '= 0)
(operator '+ 1 (direction 'left #t) (identity 0))
(operator '- 1 (direction 'left) (identity 0 #t #t 3))
(operator '* 2 (direction 'left #t) (identity 1))
(operator '/ 2 (direction 'left) (identity 1 #t))
(operator '^ 4 (direction 'right) #f (prefix #t 'expt)))))
(define current-operator-rule-set
(make-parameter (make-default-operator-rule-set)))
(define infix->prefix
(case-lambda
((expr failure)
(call/cc
(lambda (return)
(let ((rs (current-operator-rule-set)))
(map-all-list (cute infix->prefix-1 <> rs (lambda (e) (return (failure e))))
expr)))))
((expr)
(infix->prefix expr (lambda (e) #f)))))
(define (infix->prefix-1 expr rs fail)
(if (and (pair? expr) (null? (cdr expr)))
(car expr)
(let-values (((op unary?) (minimum-precedence expr rs)))
(if op
(let ->prefix ((expr (list-copy expr))
(op op)
(unary? unary?))
(define (make-prefix left op-sym right)
(define (->infix left op-sym right)
(append left (cons op-sym right)))
(let-values (((left-op left-unary?) (minimum-precedence left rs))
((right-op right-unary?) (minimum-precedence right rs)))
(let ((not-binary-only? (not (operator-prefix-binary-only? op))))
`(,(operator-prefix-symbol op)
,@(if (operator? left-op)
(if (eqv? (operator-symbol op) (operator-symbol left-op))
(if not-binary-only?
(if (and (eqv? op-sym (car left))
(pair? (cdr left))
(null? (cdr (cdr left))))
(list (->prefix left left-op left-unary?))
(cdr (->prefix left left-op left-unary?)))
(if (operator-left? op)
(list (->prefix left left-op left-unary?))
(fail expr)))
(list (->prefix left left-op left-unary?)))
(if (and (pair? left)
(null? (cdr left)))
(if (and not-binary-only?
(or (operator-left? op)
(operator-associative? op))
(pair? (car left))
(eqv? (operator-symbol op) (car (car left))))
(cdr (car left))
(list (car left)))
(fail (->infix left op-sym right))))
,@(if (operator? right-op)
(if (eqv? (operator-symbol op) (operator-symbol right-op))
(if not-binary-only?
(cdr (->prefix right right-op right-unary?))
(if (operator-right? op)
(list (->prefix right right-op right-unary?))
(fail expr)))
(list (->prefix right right-op right-unary?)))
(if (and (pair? right)
(null? (cdr right)))
(if (and not-binary-only?
(or (operator-right? op)
(operator-associative? op))
(pair? (car right))
(eqv? (operator-symbol op) (car (car right))))
(cdr (car right))
(list (car right)))
(fail (->infix left op-sym right))))))))
(cond (unary?
(let-values (((lst op+rest) (break! (cute eqv? (operator-symbol op) <>) expr)))
(let-values (((op-sym rest) (car+cdr op+rest)))
(infix->prefix-1 (append lst (list (list op-sym (infix->prefix-1 rest rs fail))))
rs
fail))))
((operator-left? op)
(let ((rev-expr (reverse! expr)))
(let-values (((rev-lst op+rev-rest) (break! (cute eqv? (operator-symbol op) <>) rev-expr)))
(let-values (((op-sym rev-rest) (car+cdr op+rev-rest)))
(make-prefix (reverse! rev-rest) op-sym (reverse! rev-lst))))))
(else
(let-values (((lst op+rest) (break! (cute eqv? (operator-symbol op) <>) expr)))
(let-values (((op rest) (car+cdr op+rest)))
(make-prefix lst op rest))))))
expr))))
(define prefix->infix
(case-lambda
((expr failure)
(let ((rs (current-operator-rule-set)))
(call-with-current-continuation
(lambda (return)
(let-values (((result _precedence) (%prefix->infix expr rs (lambda (e) (return (failure e))))))
result)))))
((expr)
(prefix->infix expr (lambda (e) #f)))))
(define (%prefix->infix expr rs failure)
(let ->infix ((expr expr))
(define (->infix-fst expr)
(let-values (((x _) (->infix expr)))
x))
(if (not (pair? expr))
(values expr -inf.0)
(let-values (((op-prefix-sym args) (car+cdr expr)))
(cond ((rule-set-prefix-ref rs op-prefix-sym)
=> (lambda (op)
(let ((p (operator-precedence op))
(op-sym (operator-symbol op)))
(cond ((null? args)
(cond ((and (not (operator-identity-inv? op))
(operator-identity op))
=> (lambda (u) (values (identity-value u) -inf.0)))
(else (failure expr))))
((null? (cdr args))
(let-values (((r-expr r-p) (->infix (car args))))
(cond ((operator-identity op)
=> (lambda (u)
(if (identity-inv? u)
(if (identity-unary? u)
(values `(,op-sym ,r-expr)
(or (operator-identity-unary-precedence op)
+inf.0))
(values `(,(identity-value (operator-identity op))
,op-sym
,@(if (or (operator-right? op)
(operator-associative? op))
(wrap-when (< r-p p) r-expr)
(wrap-when (<= r-p p) r-expr)))
p))
(values r-expr r-p))))
(else (failure expr)))))
((null? (cdr (cdr args)))
(let-values (((l-expr l-p) (->infix (car args)))
((r-expr r-p) (->infix (cadr args))))
(values `(,@(if (or (operator-left? op)
(operator-associative? op))
(wrap-when (< l-p p) l-expr)
(wrap-when (<= l-p p) l-expr))
,op-sym
,@(if (or (operator-right? op)
(operator-associative? op))
(wrap-when (< r-p p) r-expr)
(wrap-when (<= r-p p) r-expr)))
p)))
(else
(cond ((operator-associative? op)
(values (cdr (append-map! (lambda (arg)
(let-values (((x-expr x-p) (->infix arg)))
(cons op-sym (wrap-when (< x-p p) x-expr))))
args))
p))
((operator-left? op)
(let-values (((l-expr l-p) (->infix (car args))))
(values (append! (wrap-when (< l-p p) l-expr)
(append-map! (lambda (arg)
(let-values (((l-expr l-p) (->infix arg)))
(cons op-sym (wrap-when (<= l-p p) l-expr))))
(cdr args)))
p)))
((operator-right? op)
(let ((rev-args (reverse args)))
(let-values (((r-expr r-p) (->infix (car rev-args))))
(values (reverse!
(append-reverse! (wrap-when (< r-p p) r-expr)
(append-map!
(lambda (arg)
(let-values (((r-expr r-p) (->infix arg)))
(cons op-sym (wrap-when (<= r-p p) r-expr))))
(cdr rev-args))))
p))))
(else
(values (cdr (append-map! (lambda (arg)
(let-values (((x-expr x-p) (->infix arg)))
(cons op-sym (wrap-when (<= x-p p) x-expr))))
args))
p))))))))
(else (values (map ->infix-fst expr) -inf.0)))))))
(define (minimum-precedence expr rs)
(let loop ((expr expr)
(min #f)
(min-precedence +inf.0)
(min-unary? #f)
(prev #t))
(if (null? expr)
(values min min-unary?)
(cond ((rule-set-infix-ref rs (car expr))
=> (lambda (current)
(let-values (((precedence current-unary?)
(if (and (operator-identity-unary? current)
prev)
(values (operator-identity-unary-precedence current) #t)
(values (operator-precedence current) #f))))
(if (<= precedence min-precedence)
(loop (cdr expr) current precedence current-unary? current)
(loop (cdr expr) min min-precedence min-unary? current)))))
(else (loop (cdr expr) min min-precedence min-unary? #f))))))
(define (operator-identity-inv? x)
(cond ((operator-identity x) => (cut identity-inv? <>))
(else #f)))
(define (operator-identity-unary? op)
(cond ((operator-identity op) => identity-unary?)
(else #f)))
(define (operator-identity-unary-precedence op)
(cond ((operator-identity op) =>
(lambda (id)
(cond ((identity-unary-precedence id)
=> (lambda (p) p))
(else (operator-precedence op)))))
(else #f)))
(define (operator-prefix-binary-only? op)
(cond ((operator-prefix op) => prefix-binary-only?)
(else #f)))
(define (wrap-when b? x)
(if b? (list x) x))
(define (map-all-list f expr)
(f (map-cars f expr)))
(define (map-cars f expr)
(if (pair? expr)
(if (pair? (car expr))
(cons (f (map-cars f (car expr)))
(map-cars f (cdr expr)))
(cons (car expr)
(map-cars f (cdr expr))))
expr))
))