Add destructive unification
Will be used in the PAIP compiler.
author |
Steve Losh <steve@stevelosh.com> |
date |
Tue, 22 Mar 2016 14:31:35 +0000 |
parents |
2daf5fb2fe92
|
children |
3729fdede843
|
branches/tags |
(none) |
files |
bones.asd docs/03-reference.markdown package.lisp src/paip-compiled.lisp |
Changes
--- a/bones.asd Fri Mar 18 13:50:04 2016 +0000
+++ b/bones.asd Tue Mar 22 14:31:35 2016 +0000
@@ -19,5 +19,6 @@
(:file "package")
(:module "src"
:components ((:file "paip")
+ (:file "paip-compiled")
(:file "bones")))))
--- a/docs/03-reference.markdown Fri Mar 18 13:50:04 2016 +0000
+++ b/docs/03-reference.markdown Tue Mar 22 14:31:35 2016 +0000
@@ -16,6 +16,12 @@
Whether to perform an occurs check.
+### BOUND-P (function)
+
+ (BOUND-P VAR)
+
+Return whether the given variable has been bound.
+
### CLEAR-DB (function)
(CLEAR-DB)
@@ -62,9 +68,23 @@
(RULE &REST CLAUSE)
+### UNBOUND (variable)
+
+A magic constant representing an unbound variable.
+
### UNIFY (function)
(UNIFY X Y &OPTIONAL (BINDINGS NO-BINDINGS))
Unify the two terms and return bindings necessary to do so (or FAIL).
+### UNIFY! (function)
+
+ (UNIFY! X Y)
+
+Destructively unify two expressions.
+
+ Any variables in `x` and `y` may have their bindings set.
+
+
+
--- a/package.lisp Fri Mar 18 13:50:04 2016 +0000
+++ b/package.lisp Tue Mar 22 14:31:35 2016 +0000
@@ -12,6 +12,11 @@
#:fail #:no-bindings
#:*check-occurs*
+ ;; Destructive unification
+ #:unify!
+ #:unbound
+ #:bound-p
+
;; Database management
#:clear-db
#:fact
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/paip-compiled.lisp Tue Mar 22 14:31:35 2016 +0000
@@ -0,0 +1,86 @@
+(in-package #:bones.paip)
+
+;;;; Variables
+(define-constant unbound "Unbound"
+ :test #'equal
+ :documentation "A magic constant representing an unbound variable.")
+
+(defvar *var-counter* 0
+ "The number of variables created so far.")
+
+(defstruct (var (:constructor ? ())
+ (:print-function print-var))
+ (name (incf *var-counter*)) ; The variable's name (defaults to a new number)
+ (binding unbound)) ; The variable's binding (defaults to unbound)
+
+(defun* print-var ((var var) stream depth)
+ (if (or (and (numberp *print-level*)
+ (>= depth *print-level*))
+ (var-p (deref var)))
+ (format stream "?~A" (var-name var))
+ (write var :stream stream)))
+
+(defun* bound-p ((var var))
+ (:returns boolean)
+ "Return whether the given variable has been bound."
+ (not (eq (var-binding var) unbound)))
+
+(defmacro deref (expr)
+ "Chase all the bindings for the given expression in place."
+ `(progn
+ (loop :while (and (var-p ,expr) (bound-p ,expr))
+ :do (setf ,expr (var-binding ,expr)))
+ ,expr))
+
+
+;;;; Bindings
+(defvar *trail* (make-array 200 :fill-pointer 0 :adjustable t)
+ "The trail of variable bindings performed so far.")
+
+(defun* set-binding! ((var var) value)
+ (:returns (eql t))
+ "Set `var`'s binding to `value` after saving it in the trail.
+
+ Always returns `t` (success).
+
+ "
+ (when (not (eq var value))
+ (vector-push-extend var *trail*)
+ (setf (var-binding var) value))
+ t)
+
+(defun* undo-bindings! ((old-trail integer))
+ (:returns :void)
+ "Undo all bindings back to a given point in the trail.
+
+ The point is specified by giving the desired fill pointer.
+
+ "
+ (loop :until (= (fill-pointer *trail*) old-trail)
+ :do (setf (var-binding (vector-pop *trail*)) unbound))
+ (values))
+
+
+;;;; Unification
+(defun* unify! (x y)
+ (:returns boolean)
+ "Destructively unify two expressions, returning whether it was successful.
+
+ Any variables in `x` and `y` may have their bindings set.
+
+ "
+ (cond
+ ;; If they're identical objects (taking bindings into account), they unify.
+ ((eql (deref x) (deref y)) t)
+
+ ;; If they're not identical, but one is a variable, bind it to the other.
+ ((var-p x) (set-binding! x y))
+ ((var-p y) (set-binding! y x))
+
+ ;; If they're both non-empty lists, unify the cars and cdrs.
+ ((and (consp x) (consp y))
+ (and (unify! (first x) (first y))
+ (unify! (rest x) (rest y))))
+
+ ;; Otherwise they don't unify.
+ (t nil)))