eval.lisp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. ;; eval.lisp -- Evaluate parsed proposition strings
  2. ;; Copyright (C) 2024 Alexander Rosenberg
  3. ;;
  4. ;; This program is free software: you can redistribute it and/or modify
  5. ;; it under the terms of the GNU General Public License as published by
  6. ;; the Free Software Foundation, either version 3 of the License, or
  7. ;; (at your option) any later version.
  8. ;;
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. (in-package :truth-table/base)
  17. (define-condition proposition-eval-error (error)
  18. ((message :initarg :message
  19. :accessor proposition-eval-error-message)
  20. (proposition :initarg :proposition
  21. :accessor proposition-eval-error-proposition
  22. :initform nil))
  23. (:report (lambda (con stream)
  24. (with-slots (message proposition)
  25. con
  26. (format stream "~a~@[:~% ~a~]"
  27. message proposition))))
  28. (:documentation "Condition representing an error that occurred during
  29. evaluation for a proposition."))
  30. (defun operator-argument-count (oper)
  31. "Return the minimum number of arguments that OPER takes as the first value,
  32. and the maximum number (or nil for infinity) as a second value."
  33. (case oper
  34. (and (values 1 nil))
  35. (or (values 1 nil))
  36. (xor (values 1 nil))
  37. (not (values 1 1))
  38. (implies (values 2 2))
  39. (converse (values 2 2))
  40. (iff (values 2 2))
  41. (nand (values 1 nil))
  42. (nor (values 1 nil))
  43. (t (error "unknown operator: ~S" oper))))
  44. (defun logical-xor (&rest args)
  45. "Logical xor (not equal) each argument in turn with its following argument.
  46. NOTE: This is NOT a macro, there is no short circuit evaluation (all arguments
  47. are evaluated no matter what)."
  48. (loop with result = nil
  49. for arg in args do
  50. (setq result (not (eq result arg)))
  51. finally (return result)))
  52. (defun logical-and (&rest args)
  53. "Logical and (all true).
  54. NOTE: This is NOT a macro, there is no short circuit evaluation (all arguments
  55. are evaluated no matter what)."
  56. (not (member nil args)))
  57. (defun logical-or (&rest args)
  58. "Logical or (one or more true).
  59. NOTE: This is NOT a macro, so there is no short circuit evaluation (all
  60. arguments are evaluated no matter what)."
  61. (not (not (member t args))))
  62. (defun logical-implies (prop1 prop2)
  63. "Evaluate the logical implies operation on PROP1 and PROP2. That is \"if
  64. PROP1, then PROP2\".
  65. NOTE: This is NOT a macro, so there is no short circuit evaluation (all
  66. arguments are evaluated no matter what)."
  67. (if prop1 ;; only if first is true
  68. prop2 ;; eval second
  69. t)) ;; otherwise, just return true
  70. (defun check-operator-argument-count (oper args)
  71. "Raise an error if OPER cannot be called with ARGS."
  72. (multiple-value-bind (min max) (operator-argument-count oper)
  73. (let ((arg-count (length args)))
  74. (cond
  75. ((< arg-count min)
  76. (error 'proposition-eval-error
  77. :message
  78. (format nil "~s ~[takes no arguments~;requires one argument~:;~
  79. requires at least ~:*~d arguments~], ~
  80. but got ~[none~:;~:*~d~]"
  81. oper min arg-count)))
  82. ((and max (> arg-count max))
  83. (error 'proposition-eval-error
  84. :message
  85. (format nil "~s can take at most ~d argument~:p, but got ~d"
  86. oper max arg-count)))))))
  87. (defun keep-unique-expressions (mapping)
  88. "Keep only unique expressions from MAPPING, which is an alist as returned by
  89. `eval-proposition'."
  90. (loop for entry in mapping
  91. unless (assoc (car entry) output :test 'equal)
  92. collect entry into output
  93. finally (return output)))
  94. (defun eval-proposition (prop vars)
  95. "Evaluate the proposition PROP, with the alist VARS mapping variables to their
  96. values. Return the result of the proposition as the first value, and an alist
  97. mapping sub expressions to their results as the second value.
  98. NOTE: the second value does not include individual variables, literal values
  99. (true and false)."
  100. (cond
  101. ;; prop is a variable name
  102. ((stringp prop)
  103. (let ((entry (assoc prop vars :test 'equal)))
  104. (unless entry
  105. (error 'proposition-eval-error
  106. :message (format nil "unknown variable: ~S~%" prop)))
  107. (values (cdr entry) '())))
  108. ;; prop is true or false
  109. ((eq prop 'true)
  110. (values t '()))
  111. ((eq prop 'false)
  112. (values nil '()))
  113. ;; prop is a compound expression
  114. (t
  115. (loop with (oper . args) = prop
  116. for arg in args
  117. for (value sub-map) = (multiple-value-list
  118. (eval-proposition arg vars))
  119. nconc sub-map into mapping
  120. collect value into arg-values
  121. finally
  122. (check-operator-argument-count oper args)
  123. (let ((result
  124. (case oper
  125. ;; avoid using the macros `and' and `or' so we can avoid
  126. ;; using `eval'
  127. (and
  128. (apply 'logical-and arg-values))
  129. (nand
  130. (not (apply 'logical-and arg-values)))
  131. (or
  132. (apply 'logical-or arg-values))
  133. (nor
  134. (not (apply 'logical-or arg-values)))
  135. (xor
  136. (apply 'logical-xor arg-values))
  137. (not
  138. (not (car arg-values)))
  139. (implies
  140. (logical-implies (car arg-values)
  141. (second arg-values)))
  142. (converse
  143. ;; this is just implies with the arguments flipped
  144. (logical-implies (second arg-values)
  145. (car arg-values)))
  146. (iff
  147. (eq (car arg-values) ;; both must have the same value
  148. (second arg-values))))))
  149. (return (values result
  150. (keep-unique-expressions
  151. (cons (cons prop result) mapping)))))))))