;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- Author: Peter Norvig ;;; File: options.lisp; Date: 2/Feb/95 (in-package :cl-user) ;;;; OPTIONS: FACILITY FOR DEFINING, SETTING. AND QUERYING OPTIONS ;;; There are better mechanisms for this in CLIM and in LispWorks, ;;; but I wanted something portable to bare CL. The public interface is: ;;; (get-option name) Fetch the value for this option name. ;;; (set-option name value) Set the value. ;;; (new-options :name val...) Define a new set of options. ;;; (member-of-option item name) Is item in name's option value? ;;; *options* The currently used set of options. ;;; *default-options* Holds default values for options. ;;; Here is the implementation: (defstruct (option (:type list)) name value type doc) (deftype boolean () '(member t nil)) (defparameter *default-options* `( ;; Options for the conversion from Lisp to Dylan (:empty-as |\#()| (member |\#()| |\#f|) "Should () translate as #() or #f") (:nil-as |\#f| (member |\#()| |\#f|) "Should NIL translate as #() or #f") (:when-as if (member if when) "Should WHEN translate to IF or to WHEN") (:unless-as if (member if unless) "Should UNLESS translate to IF or to UNLESS") (:cond-as if (member if case) "Should COND translate to IF or to CASE") (:defun-as define-method (member define-function define-method) "Should DEFUN translate to DEFINE METHOD or to DEFINE FUNCTION") (:convert-slot-value t boolean "Should (slot-value x slot) become 'x.slot'") (:use-cl-sequence-functions nil boolean "Should CL Library sequence functions be used when there is a Dylan function") (:class-name-arguments-to-condition-functions t boolean "Should, e.g., #'signal worry about calls like (signal 'condition ...)") (:macroexpand-hard-loops t boolean "If we can't convert a LOOP, should we macroexpand it") (:macroexpand-hard-format-strings t boolean "If we can't convert a format string, should we use FORMATTER") (:only-binary-arithmetic-ops t boolean "Should we assume that, e.g., #'+ will only be applied to two arguments") (:obey-in-package t boolean "Should we switch packages when encountering an IN-PACKAGE") (:errors-inline t boolean "Should warnings appear as comments in the Dylan code") ;; Options for pretty-printing style (indenting, etc.) (:print-package nil boolean "Should the package of a symbol be printed [rather than ignored]") (:tab-stop 2 (integer 1 8) "Number of spaces to indent for each block") (:single-returns-wrapped t boolean "Should one-element return lists print as '=> (x)' [rather than '=> x']") (:prefer-dot-notation nil boolean "Should we print most everything as 'x.f' [rather than 'f(x)']") (:undotted-functions (make singleton signal error warning assert open close) (or (member t) list) "A list of functions that never get printed in dot notation") (:semicolon-before-end t boolean "Should we print the semicolon in 'x; end' [rather than 'x end']") (:space-in-call nil boolean "Should we print a function call as 'f (x)' [rather than 'f(x)']") (:comments // (member // /*) "Should comments print with '//' or '/*'") (:end-name t (or t nil) "Should we print 'end method f' [rather than just 'end method']") (:end-construct t (or (member t) list) "A list of constructs, e.g. (block class), for which we print 'end block' [rather than just 'end'], or T to cover every construct") )) (defparameter *options* (copy-tree *default-options*)) (defun new-options (&rest inits &key (default *default-options*) (? nil) &allow-other-keys) ;; Build and install a new options list. You can: ;; (1) specify a default with, e.g., :default *old-options* ;; (2) override values with, e.g., :unless-as 'if :tab-stop 4 ;; (3) set all values to "ask user" with :? t (setf *options* (copy-tree default)) (when ? (dolist (option *options*) (setf (option-value option) :?))) (loop for (key val) on inits by 'cddr do (unless (member key '(:? :default)) (set-option key val))) *options*) (defun set-option (name value &optional (ask? t)) "Set an option name to a value, if legal. Returns t if legal." (let ((option (find-option name))) (cond ((null option) (warn "No such option name as ~A; ignored." name) nil) ((legal-option-value? name value) (setf (option-value option) value) t) (ask? (format *query-io* "~&The legal values are ~A" (type->string (option-type option))) (get-option name t)) (t nil)))) (defun get-option (name &optional (ask? t)) "Get the value of the named option, asking if necessary." (let* ((option (find-option name)) (value (if option (option-value option)))) (cond ((null option) (warn "No such option name as ~A; ignored." name)) ((and (eq value :?) ask?) (format *query-io* "~&(Type a one-time answer like ~S, or type ALWAYS ~:*~S ~%~ to avoid this question in the future.)~%" (let ((*options* *default-options*)) (get-option name))) (format *query-io* "~A? " (option-doc option)) (let* ((value (read *query-io*))) (cond ((eq value 'always) (set-option name (read *query-io*))) ((legal-option-value? name value) value) (t (set-option name value t))))) (t value)))) (defun find-option (name) (assoc name *options*)) (defun legal-option-value? (name value) (or (eq value :?) (typep value (option-type (find-option name))))) (defun member-of-option (item name) "Is ITEM a member of (OPTION NAME), or is (OPTION NAME) equal to t?" (or (eq (get-option name) t) (member item (get-option name)))) (defun type->string (type) (cond ((eq type 'boolean) "T or NIL (for yes or no, respectively)") ((equal type '(or (member t) list)) "either a list of names, or T to indicate any name") ((atom type) (format nil "a ~A" type)) ((eq (first type) 'integer) (format nil "an integer from ~D to ~D" (second type) (third type))) ((and (starts-with type 'member) (= (length type) 2)) (format nil "~S" (second type))) ((starts-with type 'member) (format nil "one of the set {~{~S~^, ~}}" (rest type))) ((starts-with type 'and) (format nil "~{~A~^ and ~}" (mapcar #'type->string (rest type)))) ((starts-with type 'or) (format nil "~{~A~^ or ~}" (mapcar #'type->string (rest type))))))