(defmacro define-rule-type (type-name)
(let ((rule-gen-name (symbolicate 'define- type-name '-rule))
(apply-of-type-name (symbolicate 'apply-rules-of- type-name)))
`(progn
(pushnew ',type-name *rules-types*)
(defmacro ,rule-gen-name (name (applicability-type &rest applicability-test) (&rest arguments) &body action)
`(define-rule ,name ,(quote ,type-name) (,applicability-type ,@applicability-test) (,@arguments) ,@action))
(defun ,apply-of-type-name (data)
(let ((action (rule-action
(first (remove-if-not #'(lambda (rule)
(handler-bind ((sb-int:simple-program-error #'(lambda (&rest stuff) nil)))
(apply (rule-test rule) data))
)
(get-rules-of-type ',type-name))))))
(apply action data)))
',type-name)))
(defmacro define-rule (name type (applicability-type &rest applicability-test-arguments) (&rest arguments) &body action)
(let ((applicability-test (generate-applicability-test applicability-type applicability-test-arguments name))
(applicability-test-name (symbolicate name '-applyable-p))
(action-name (symbolicate 'apply- name '-action)))
`(progn
,applicability-test
(defun ,action-name (,@arguments)
,@action)
(push (make-instance '<rule>
:name ',name
:type ',type
:test #',applicability-test-name
:action #',action-name)
,*rules*)
',name)))
(defun get-rules-of-type (type)
(remove-if-not #'(lambda (rule)
(equal type
(rule-type rule)))
,*rules*))
<<type-testing>>
<<arity-testing>>
<<arg-parsing-arity-testing>>
<<length-testing>>
<<complex-testing>>
<<arg-parsing-complex-testing>>
(defun generate-applicability-test (type type-arguments name)
(check-type type keyword)
(check-type type-arguments cons)
(check-type name symbol)
(let ((test-name (symbolicate name '-applyable-p)))
(ecase type
(:type
(make-type-test test-name type-arguments))
(:arity
(make-arity-test test-name type-arguments))
(:complex-arity
(make-complex-arity test-name type-arguments))
(:length
(make-length-test test-name type-arguments))
(:complex
(make-complex-test test-name type-arguments))
(:complex-arg-parsing
(make-complex-arg-parsing-test test-name type-arguments)))))
(defun make-type-test (name arguments)
(destructuring-bind (type-expression) arguments
`(defun ,name (data)
(typep data ',type-expression))))
(defun make-arity-test (name arguments)
(destructuring-bind (function-name arity &optional (arity-type '=)) arguments
(check-type arity-type (member = > >=))
`(defun ,name (function &rest arguments &aux (arg-count (length arguments)))
(and (eq function ',function-name)
(,arity-type arg-count ,arity)))))
(defun make-complex-arity (name arguments)
(destructuring-bind (function-name arity (&rest arguments) &body body) arguments
`(defun ,name (function &rest arguments &aux (arg-count (length arguments)))
(and (eq function ',function-name)
(= arg-count ,arity)
(destructuring-bind (,@arguments) arguments
,@body)))))
(defun make-length-test (name arguments)
(destructuring-bind (length &optional (test '=)) arguments
`(defun ,name (&rest data)
(,test (length data) ,length))))
(defun make-complex-test (name arguments)
(destructuring-bind (&body test-body) arguments
`(defun ,name (datum)
,@test-body)))
(defun make-complex-arg-parsing-test (name arguments)
(destructuring-bind ((&rest arguments) &body body) arguments
`(defun ,name (,@arguments)
,@body)))
(defclass <rule> ()
((name :initarg :name
:accessor rule-name
:type symbol)
(type :initarg :type
:accessor rule-type
:type symbol)
(applicability-test :initarg :test
:accessor rule-test
:type function)
(action :initarg :action
:accessor rule-action
:type function)))
(defvar *rules* nil)
(defvar *rules-types* nil)
;;;; rules.lisp
;;;;
;;;; Copyright (c) 2015 Samuel W. Flint <swflint@flintfam.org>
(defpackage #:rules
(:use #:cl)
(:import-from #:alexandria
#:symbolicate)
(:export #:define-rule-type
#:define-rule))
(in-package #:rules)
;;; "rules" goes here.
<<rule-storage>>
<<applicability-tests>>
<<rule-definition>>
<<rule-retrieval>>
<<rule-types>>
;;; End rules