view lisp/behavior.el @ 5353:38e24b8be4ea

Improve the lexical scoping in #'block, #'return-from. lisp/ChangeLog addition: 2011-02-07 Aidan Kehoe <kehoea@parhasard.net> * bytecomp.el: * bytecomp.el (byte-compile-initial-macro-environment): Shadow `block', `return-from' here, we implement them differently when byte-compiling. * bytecomp.el (byte-compile-active-blocks): New. * bytecomp.el (byte-compile-block-1): New. * bytecomp.el (byte-compile-return-from-1): New. * bytecomp.el (return-from-1): New. * bytecomp.el (block-1): New. These are two aliases that exist to have their own associated byte-compile functions, which functions implement `block' and `return-from'. * cl-extra.el (cl-macroexpand-all): Fix a bug here when macros in the environment have been compiled. * cl-macs.el (block): * cl-macs.el (return): * cl-macs.el (return-from): Be more careful about lexical scope in these macros. * cl.el: * cl.el ('cl-block-wrapper): Removed. * cl.el ('cl-block-throw): Removed. These aren't needed in code generated by this XEmacs. They shouldn't be needed in code generated by XEmacs 21.4, but if it turns out the packages do need them, we can put them back. 2011-01-30 Mike Sperber <mike@xemacs.org> * font-lock.el (font-lock-fontify-pending-extents): Don't fail if `font-lock-mode' is unset, which can happen in the middle of `revert-buffer'. 2011-01-23 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (delete): * cl-macs.el (delq): * cl-macs.el (remove): * cl-macs.el (remq): Don't use the compiler macro if these functions were given the wrong number of arguments, as happens in lisp-tests.el. * cl-seq.el (remove, remq): Removed. I added these to subr.el, and forgot to remove them from here. 2011-01-22 Aidan Kehoe <kehoea@parhasard.net> * bytecomp.el (byte-compile-setq, byte-compile-set): Remove kludge allowing keywords' values to be set, all the code that does that is gone. * cl-compat.el (elt-satisfies-test-p): * faces.el (set-face-parent): * faces.el (face-doc-string): * gtk-font-menu.el: * gtk-font-menu.el (gtk-reset-device-font-menus): * msw-font-menu.el: * msw-font-menu.el (mswindows-reset-device-font-menus): * package-get.el (package-get-installedp): * select.el (select-convert-from-image-data): * sound.el: * sound.el (load-sound-file): * x-font-menu.el (x-reset-device-font-menus-core): Don't quote keywords, they're self-quoting, and the win from backward-compatibility is sufficiently small now that the style problem overrides it. 2011-01-22 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (block, return-from): Require that NAME be a symbol in these macros, as always documented in the #'block docstring and as required by Common Lisp. * descr-text.el (unidata-initialize-unihan-database): Correct the use of non-symbols in #'block and #'return-from in this function. 2011-01-15 Aidan Kehoe <kehoea@parhasard.net> * cl-extra.el (concatenate): Accept more complicated TYPEs in this function, handing the sequences over to #'coerce if we don't understand them here. * cl-macs.el (inline): Don't proclaim #'concatenate as inline, its compiler macro is more useful than doing that. 2011-01-11 Aidan Kehoe <kehoea@parhasard.net> * subr.el (delete, delq, remove, remq): Move #'remove, #'remq here, they don't belong in cl-seq.el; move #'delete, #'delq here from fns.c, implement them in terms of #'delete*, allowing support for sequences generally. * update-elc.el (do-autoload-commands): Use #'delete*, not #'delq here, now the latter's no longer dumped. * cl-macs.el (delete, delq): Add compiler macros transforming #'delete and #'delq to #'delete* calls. 2011-01-10 Aidan Kehoe <kehoea@parhasard.net> * dialog.el (make-dialog-box): Correct a misplaced parenthesis here, thank you Mats Lidell in 87zkr9gqrh.fsf@mail.contactor.se ! 2011-01-02 Aidan Kehoe <kehoea@parhasard.net> * dialog.el (make-dialog-box): * list-mode.el (display-completion-list): These functions used to use cl-parsing-keywords; change them to use defun* instead, fixing the build. (Not sure what led to me not including this change in d1b17a33450b!) 2011-01-02 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (define-star-compiler-macros): Make sure the form has ITEM and LIST specified before attempting to change to calls with explicit tests; necessary for some tests in lisp-tests.el to compile correctly. (stable-union, stable-intersection): Add compiler macros for these functions, in the same way we do for most of the other functions in cl-seq.el. 2011-01-01 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (dolist, dotimes, do-symbols, macrolet) (symbol-macrolet): Define these macros with defmacro* instead of parsing the argument list by hand, for the sake of style and readability; use backquote where appropriate, instead of calling #'list and and friends, for the same reason. 2010-12-30 Aidan Kehoe <kehoea@parhasard.net> * x-misc.el (device-x-display): Provide this function, documented in the Lispref for years, but not existing previously. Thank you Julian Bradfield, thank you Jeff Mincy. 2010-12-30 Aidan Kehoe <kehoea@parhasard.net> * cl-seq.el: Move the heavy lifting from this file to C. Dump the cl-parsing-keywords macro, but don't use defun* for the functions we define that do take keywords, dynamic scope lossage makes that not practical. * subr.el (sort, fillarray): Move these aliases here. (map-plist): #'nsublis is now built-in, but at this point #'eql isn't necessarily available as a test; use #'eq. * obsolete.el (cl-delete-duplicates): Make this available for old compiler macros and old code. (memql): Document that this is equivalent to #'member*, and worse. * cl.el (adjoin, subst): Removed. These are in C. 2010-12-30 Aidan Kehoe <kehoea@parhasard.net> * simple.el (assoc-ignore-case): Remove a duplicate definition of this function (it's already in subr.el). * iso8859-1.el (char-width): On non-Mule, make this function equivalent to that produced by (constantly 1), but preserve its docstring. * subr.el (subst-char-in-string): Define this in terms of #'substitute, #'nsubstitute. (string-width): Define this using #'reduce and #'char-width. (char-width): Give this a simpler definition, it makes far more sense to check for mule at load time and redefine, as we do in iso8859-1.el. (store-substring): Implement this in terms of #'replace, now #'replace is cheap. 2010-12-30 Aidan Kehoe <kehoea@parhasard.net> * update-elc.el (lisp-files-needed-for-byte-compilation) (lisp-files-needing-early-byte-compilation): cl-macs belongs in the former, not the latter, it is as fundamental as bytecomp.el. 2010-12-30 Aidan Kehoe <kehoea@parhasard.net> * cl.el: Provde the Common Lisp program-error, type-error as error symbols. This doesn't nearly go far enough for anyone using the Common Lisp errors. 2010-12-29 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (delete-duplicates): If the form has an incorrect number of arguments, don't attempt a compiler macroexpansion. 2010-12-29 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (cl-safe-expr-p): Forms that start with the symbol lambda are also safe. 2010-12-29 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (= < > <= >=): For these functions' compiler macros, the optimisation is safe even if the first and the last arguments have side effects, since they're only used the once. 2010-12-29 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (inline-side-effect-free-compiler-macros): Unroll a loop here at macro-expansion time, so these compiler macros are compiled. Use #'eql instead of #'eq in a couple of places for better style. 2010-12-29 Aidan Kehoe <kehoea@parhasard.net> * cl-extra.el (notany, notevery): Avoid some dynamic scope stupidity with local variable names in these functions, when they weren't prefixed with cl-; go into some more detail in the doc strings. 2010-12-29 Aidan Kehoe <kehoea@parhasard.net> * byte-optimize.el (side-effect-free-fns): #'remove, #'remq are free of side-effects. (side-effect-and-error-free-fns): Drop dot, dot-marker from the list. 2010-11-17 Aidan Kehoe <kehoea@parhasard.net> * cl-extra.el (coerce): In the argument list, name the first argument OBJECT, not X; the former name was always used in the doc string and is clearer. Handle vector type specifications which include the length of the target sequence, error if there's a mismatch. * cl-macs.el (cl-make-type-test): Handle type specifications starting with the symbol 'eql. 2010-11-14 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (eql): Don't remove the byte-compile property of this symbol. That was necessary to override a bug in bytecomp.el where #'eql was confused with #'eq, which bug we no longer have. If neither expression is constant, don't attempt to handle the expression in this compiler macro, leave it to byte-compile-eql, which produces better code anyway. * bytecomp.el (eq): #'eql is not the function associated with the byte-eq byte code. (byte-compile-eql): Add an explicit compile method for this function, for cases where the cl-macs compiler macro hasn't reduced it to #'eq or #'equal. 2010-10-25 Aidan Kehoe <kehoea@parhasard.net> Add compiler macros and compilation sanity-checking for various functions that take keywords. * byte-optimize.el (side-effect-free-fns): #'symbol-value is side-effect free and not error free. * bytecomp.el (byte-compile-normal-call): Check keyword argument lists for sanity; store information about the positions where keyword arguments start using the new byte-compile-keyword-start property. * cl-macs.el (cl-const-expr-val): Take a new optional argument, cl-not-constant, defaulting to nil, in this function; return it if the expression is not constant. (cl-non-fixnum-number-p): Make this into a separate function, we want to pass it to #'every. (eql): Use it. (define-star-compiler-macros): Use the same code to generate the member*, assoc* and rassoc* compiler macros; special-case some code in #'add-to-list in subr.el. (remove, remq): Add compiler macros for these two functions, in preparation for #'remove being in C. (define-foo-if-compiler-macros): Transform (remove-if-not ...) calls to (remove ... :if-not) at compile time, which will be a real win once the latter is in C. (define-substitute-if-compiler-macros) (define-subst-if-compiler-macros): Similarly for these functions. (delete-duplicates): Change this compiler macro to use #'plists-equal; if we don't have information about the type of SEQUENCE at compile time, don't bother attempting to inline the call, the function will be in C soon enough. (equalp): Remove an old commented-out compiler macro for this, if we want to see it it's in version control. (subst-char-in-string): Transform this to a call to nsubstitute or nsubstitute, if that is appropriate. * cl.el (ldiff): Don't call setf here, this makes for a load-time dependency problem in cl-macs.el 2010-06-14 Stephen J. Turnbull <stephen@xemacs.org> * term/vt100.el: Refer to XEmacs, not GNU Emacs, in permissions. * term/bg-mouse.el: * term/sup-mouse.el: Put copyright notice in canonical "Copyright DATE AUTHOR" form. Refer to XEmacs, not GNU Emacs, in permissions. * site-load.el: Add permission boilerplate. * mule/canna-leim.el: * alist.el: Refer to XEmacs, not APEL/this program, in permissions. * mule/canna-leim.el: Remove my copyright, I've assigned it to the FSF. 2010-06-14 Stephen J. Turnbull <stephen@xemacs.org> * gtk.el: * gtk-widget-accessors.el: * gtk-package.el: * gtk-marshal.el: * gtk-compose.el: * gnome.el: Add copyright notice based on internal evidence. 2010-06-14 Stephen J. Turnbull <stephen@xemacs.org> * easymenu.el: Add reference to COPYING to permission notice. * gutter.el: * gutter-items.el: * menubar-items.el: Fix typo "Xmacs" in permissions notice. 2010-06-14 Stephen J. Turnbull <stephen@xemacs.org> * auto-save.el: * font.el: * fontconfig.el: * mule/kinsoku.el: Add "part of XEmacs" text to permission notice. 2010-10-14 Aidan Kehoe <kehoea@parhasard.net> * byte-optimize.el (side-effect-free-fns): * cl-macs.el (remf, getf): * cl-extra.el (tailp, cl-set-getf, cl-do-remf): * cl.el (ldiff, endp): Tighten up Common Lisp compatibility for #'ldiff, #'endp, #'tailp; add circularity checking for the first two. #'cl-set-getf and #'cl-do-remf were Lisp implementations of #'plist-put and #'plist-remprop; change the names to aliases, changes the macros that use them to using #'plist-put and #'plist-remprop directly. 2010-10-12 Aidan Kehoe <kehoea@parhasard.net> * abbrev.el (fundamental-mode-abbrev-table, global-abbrev-table): Create both these abbrev tables using the usual #'define-abbrev-table calls, rather than attempting to special-case them. * cl-extra.el: Force cl-macs to be loaded here, if cl-extra.el is being loaded interpreted. Previously other, later files would redundantly call (load "cl-macs") when interpreted, it's more reasonable to do it here, once. * cmdloop.el (read-quoted-char-radix): Use defcustom here, we don't have any dump-order dependencies that would prevent that. * custom.el (eval-when-compile): Don't load cl-macs when interpreted or when byte-compiling, rely on cl-extra.el in the former case and the appropriate entry in bytecomp-load-hook in the latter. Get rid of custom-declare-variable-list, we have no dump-time dependencies that would require it. * faces.el (eval-when-compile): Don't load cl-macs when interpreted or when byte-compiling. * packages.el: Remove some inaccurate comments. * post-gc.el (cleanup-simple-finalizers): Use #'delete-if-not here, now the order of preloaded-file-list has been changed to make it available. * subr.el (custom-declare-variable-list): Remove. No need for it. Also remove a stub define-abbrev-table from this file, given the current order of preloaded-file-list there's no need for it. 2010-10-10 Aidan Kehoe <kehoea@parhasard.net> * bytecomp.el (byte-compile-constp) Forms quoted with FUNCTION are also constant. (byte-compile-initial-macro-environment): In #'the, if FORM is constant and does not match TYPE, warn at byte-compile time. 2010-10-10 Aidan Kehoe <kehoea@parhasard.net> * backquote.el (bq-vector-contents, bq-list*): Remove; the former is equivalent to (append VECTOR nil), the latter to (list* ...). (bq-process-2): Use (append VECTOR nil) instead of using #'bq-vector-contents to convert to a list. (bq-process-1): Now we use list* instead of bq-list * subr.el (list*): Moved from cl.el, since it is now required to be available the first time a backquoted form is encountered. * cl.el (list*): Move to subr.el. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * test-harness.el (Check-Message): Add an omitted comma here, thank you the buildbot. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * hash-table.el (hash-table-key-list, hash-table-value-list) (hash-table-key-value-alist, hash-table-key-value-plist): Remove some useless #'nreverse calls in these files; our hash tables have no order, it's not helpful to pretend they do. * behavior.el (read-behavior): Do the same in this file, in some code evidently copied from hash-table.el. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * info.el (Info-insert-dir): * format.el (format-deannotate-region): * files.el (cd, save-buffers-kill-emacs): Use #'some, #'every and related functions for applying boolean operations to lists, instead of rolling our own ones that cons and don't short-circuit. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * bytecomp.el (byte-compile-initial-macro-environment): * cl-macs.el (the): Rephrase the docstring, make its implementation when compiling files a little nicer. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * descr-text.el (unidata-initialize-unicodedata-database) (unidata-initialize-unihan-database, describe-char-unicode-data) (describe-char-unicode-data): Wrap calls to the database functions with (with-fboundp ...), avoiding byte compile warnings on builds without support for the database functions. (describe-char): (reduce #'max ...), not (apply #'max ...), no need to cons needlessly. (describe-char): Remove a redundant lambda wrapping #'extent-properties. (describe-char-unicode-data): Call #'nsubst when replacing "" with nil in the result of #'split-string, instead of consing inside mapcar. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * x-faces.el (x-available-font-sizes): * specifier.el (let-specifier): * package-ui.el (pui-add-required-packages): * msw-faces.el (mswindows-available-font-sizes): * modeline.el (modeline-minor-mode-menu): * minibuf.el (minibuf-directory-files): Replace the O2N (delq nil (mapcar (lambda (W) (and X Y)) Z)) with the ON (mapcan (lambda (W) (and X (list Y))) Z) in these files. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * cl-macs.el (= < > <= >=): When these functions are handed more than two arguments, and those arguments have no side effects, transform to a series of two argument calls, avoiding funcall in the byte-compiled code. * mule/mule-cmds.el (finish-set-language-environment): Take advantage of this change in a function called 256 times at startup. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * bytecomp.el (byte-compile-function-form, byte-compile-quote) (byte-compile-quote-form): Warn at compile time, and error at runtime, if a (quote ...) or a (function ...) form attempts to quote more than one object. 2010-09-16 Aidan Kehoe <kehoea@parhasard.net> * byte-optimize.el (byte-optimize-apply): Transform (apply 'nconc (mapcar ...)) to (mapcan ...); warn about use of the first idiom. * update-elc.el (do-autoload-commands): * packages.el (packages-find-package-library-path): * frame.el (frame-list): * extents.el (extent-descendants): * etags.el (buffer-tag-table-files): * dumped-lisp.el (preloaded-file-list): * device.el (device-list): * bytecomp-runtime.el (proclaim-inline, proclaim-notinline) Use #'mapcan, not (apply #'nconc (mapcar ...) in all these files. * bytecomp-runtime.el (eval-when-compile, eval-and-compile): In passing, mention that these macros also evaluate the body when interpreted. tests/ChangeLog addition: 2011-02-07 Aidan Kehoe <kehoea@parhasard.net> * automated/lisp-tests.el: Test lexical scope for `block', `return-from'; add a Known-Bug-Expect-Failure for a contorted example that fails when byte-compiled.
author Aidan Kehoe <kehoea@parhasard.net>
date Mon, 07 Feb 2011 12:01:24 +0000
parents 2def0d83a5e3
children 308d34e9f07d
line wrap: on
line source

;;; behavior.el --- consistent interface onto packages

;; Copyright (C) 2000, 2001, 2002, 2003 Ben Wing.

;; Author: Ben Wing
;; Maintainer: XEmacs Development Team
;; Keywords: internal, dumped

;; This file is part of XEmacs.

;; XEmacs 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 2, or (at your option)
;; any later version.

;; XEmacs 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 XEmacs; see the file COPYING.  If not, write to the 
;; Free Software Foundation, 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Synched up with: Not in FSF.

;;; Authorship:

;; Created July 2000 by Ben Wing.

;;; Commentary:

;; This file is dumped with XEmacs.

;; This file is part of the "Behaviors" project and is a work in progress.
;; The purpose of the project is to provide (a) a consistent interface (at
;; the API level) onto the functionality provided by packages, and (b) an
;; easy-to-use user interface for this functionality, something that
;; *really works*.
;;
;; First, what characteristics do/should packages have? (NOTE: In this
;; discussion below, `package' and `behavior' are being used more or less
;; interchangeably.  Eventually this will get resolved.)

;; 1) A file, or one or more file, containing the code of the package.  In
;;    addition, a "head" file in the case that the package needs to be
;;    loaded in order to get its functionality (e.g. "load-to-enable"
;;    packages -- an abomination that is tolerated only with severe
;;    displeasure).
;; 2) A Lisp name -- a fairly short symbol (2-3 words max), uncapitalized,
;;    without use of excessive abbreviation and with words set off by
;;    dashes.  This should be the same as the name of the topmost custom
;;    group associated with the package (see next item), and preferably the
;;    same as the common prefix used for variables defined by your package
;;    and the name of the head file of the package.
;; 3) Associated custom group listing the settings associated with the package.
;; 4) Enable and disable methods for turning on or off the functionality of
;;    the package, if it's amenable to such a model.  Most packages are of two
;;    types:
;; 
;;    (a) They add some functionality to XEmacs, which is incorporated
;;    into and makes changes to the normal functionality of XEmacs.  Once the
;;    package is enabled, the user doesn't have to do anything specific for
;;    the package to do its thing -- it happens automatically if the user is
;;    using the area whose behavior has been changed.  These include packages
;;    such as `avoid' (which makes the mouse poointer move when the cursor
;;    gets too close), EFS (which adds the ability to treat an FTP site as
;;    part of the local file system), the packages that supply the
;;    mode-specific handling for various files, etc
;; 
;;    (b) They provide functionality in the form of specific command to be
;;    invoked.  This can be as simple as the `hippie-expand' command (tries
;;    lots of different expansion methods for the text before point, to
;;    try and get a result) and as complicated as GNUS or VM.
;; 
;;    Some packages might provide both -- you can enable them and they
;;    incorporate some functionality into the XEmacs base, but while
;;    they're enabled they provide certain commands.  #### We need some
;;    thought here, and case-by-case analysis, to determine if this really
;;    makes sense -- can the enable/disable be removed and whatever needs
;;    to happen incorporated as part of the command?  can the
;;    enable/disable just added to the commands?
;; 
;; 5) Packages of type (b) just above will have a list of commands that can be
;;    run.  They should be in standard menubar format -- i.e. just like a
;;    submenu, but without the initial string indidicating the name of the
;;    menu.
;; 6) Short doc string, for use in a menu item.  *NOT* necessarily the same
;;    as the documentation for the Custom group, which is often too long.
;; 7) Long documentation.
;; 
;; Good package etiquette:
;; 
;; 
;; -- Never mess with the menu yourself, or try to "cheat" by putting yourself
;;    high up in the hierarchy, e.g. at the top-level or directly off a
;;    top-level group that expects to contain only groups of groups, not
;;    end-level groups.
;; 
;; -- Never use the `override-behavior' commands or the like for specifying
;; (in an overriding fashion) the exact appearance of the hierarchies.
;; 
;; -- For type (a), with enable/disable methods:
;; 
;;    (a) Loading the file should NOT DO ANYTHING.  Not enable, not add hooks,
;;        nothing.
;;    (b) Both enable and disable hooks must exist.  The disable hook must
;;        completely reset the environment to how it was before the package
;;        was enabled.  This includes restoring the prior bindings for
;;        modified key bindings.  #### We need some helper function to assist
;;        with remembering the old key bindings and putting them back only
;;        when new key bindings haven't been made -- but recognize when those
;;        new key bondings were attached as a result of loading another
;;        package, so that after any order of loading and unloading a series
;;        of packages, the original bindings will eventually occur. (Something
;;        like `advice' for key definitions.) Replacement of functions should
;;        happen through `advice'.
;; 
;;    We recognize that many packages out there don't follow these precepts at
;;    all.  Many or most of them are install-only, often happening
;;    automatically when the file is loaded.  Converting these will be a step
;;    at a time: First, redo the latter type so that the initialization code
;;    is put into a function and not run automatically upon load.  Next step,
;;    try to provide some sort of disable.  Third step, work on making sure
;;    that disable removes *everything* and enable puts it all back.  Fourth
;;    step, work on properly advising keys and functions.
;; 

;; Comparison/Integration with Custom:

;; Custom only handles variable settings, and has no concept of standard
;; enable/disable methods for a package, a standard way of specifying
;; package documentation, or a list of commands associated with a package.
;; Also, its groups do not always map very well onto packages and the
;; resulting hierarchy is too big, confusing, difficult-to-navigate, and
;; incoherent.  More generally it does not address at all the basic problem
;; that a hierarchy created in a decentralized fashion -- and by a large
;; number of authors, some more competent than others -- will inevitably be
;; incoherent when put together.
;;

;; In general, ease-of-use was not the overarching goal of Custom.  The
;; primary goal of Custom seems to have been to provide a consistent interface
;; and get all the packages to use it.  Ease-of-use -- or even following
;; established user-interface standards -- has taken a far-distant second, and
;; appears in many respects to be an afterthought that never had any serious
;; effort investigated into it.
;; 
;; The eventual intent of this project is to integrate with custom.  The final
;; intent of integration is that this project subsumes Custom completely,
;; making Custom the unified, user-friendly means of controlling XEmacs that
;; has never properly existed.  However, that will take a lot of work.  For
;; the meantime, the plan is to develop the Behavior subsystem independent of
;; Custom, with ease-of-use as the primary goal, and get it to the point where
;; it encompasses most packages out there, has stabilized its interface, and
;; works well.  At that point, we will consider integration with Custom. (Note
;; that the hard part of the Behavior work is not actually behaviorizing the
;; packages, but developing the interface itself.)
;; 
;; As for integrating with Custom -- ideally that would mean simply extending
;; defgroup, but that is not really possible given that backward-compatibility
;; would not work -- existing versions of `defgroup' give an error when
;; presented with an unknown keyword.  In practice, then, this might mean that
;; a separate `define-behavior' command (or `defpackage', or the like) will
;; still exist.

;;; Code:

;; Hash table mapping behavior names to property lists, with entries for
;; :group, :custom-group, :short-doc, :require, :enable, :disable,
;; and :commands.
(defconst behavior-hash-table (make-hash-table))
;; Hash table mapping groups to property lists (entries for :group, :children,
;; :short-doc).
(defconst behavior-group-hash-table (make-hash-table))
;; Hash table with override information for groups.
;; :short-doc).
(defconst behavior-override-hash-table (make-hash-table))

(defvar within-behavior-enabling-disabling nil)

(defgroup behaviors nil
  "Behaviors -- high-level functionality interface.")

;; List of enabled behaviors.
(defcustom enabled-behavior-list nil
  "List of currently enabled behaviors.
Normally, don't set it directly; use `enable-behavior' or `disable-behavior'."
  :initialize #'set-default
  :set #'(lambda (sym val)
	   (if within-behavior-enabling-disabling
	       (set sym val)
	     (let* ((old-val enabled-behavior-list)
		    (disable-list (set-difference old-val val))
		    (enable-list (set-difference val old-val)))
	       (dolist (b disable-list)
		 (disable-behavior b t))
	       (dolist (b enable-list)
		 (enable-behavior b t))
	       (assert (equal (sort (copy-sequence enabled-behavior-list) 'string-lessp)
			      (sort (copy-sequence val) 'string-lessp))))))
  :type '(repeat (symbol :tag "Behavior"))
  :group 'behaviors)


(defvar behavior-history nil
  "History of entered behaviors.")

(defun behavior-group-p (group)
  "Non-nil if GROUP is the name of a valid behavior group."
  (not (null (gethash group behavior-group-hash-table))))

(defun check-behavior-group (group)
  "Verify that GROUP is a valid behavior group, or nil.
Return GROUP if so."
  (or (behavior-group-p group)
      (error 'invalid-argument "Invalid behavior group" group))
  group)

(defun* define-behavior (name doc-string &key
			 group custom-group
			 (short-doc
			  (capitalize-string-as-title
			   (replace-in-string (symbol-name name) "-" " ")))
			 require enable disable commands
			 &allow-other-keys)
  ;; We allow other keys to allow for the possibility of extensions by
  ;; later versions of XEmacs.  Packages should be able to support those
  ;; extensions without worrying about causing problems with older versions
  ;; of XEmacs.
  "Define a behavior named NAME.
DOC-STRING must be specified, a description of what the behavior does
when it's enabled and how to further control it (typically through
custom variables).  Accepted keywords are

:group	    Symbol naming the behavior group this behavior is within.
:custom-group Symbol naming the custom group containing the options that
            can be set in association with this behavior.  If not specified,
            the custom group with the same name as the behavior will be
            used, if it exists.
:short-doc  A \"pretty\" version of the name, for use in menus.  If omitted
              a prettified name will be generated.
:require    A single symbol or a list of such symbols, which need to be
              present at enable time, or will be loaded using `require'.
:enable     A function of no variables, which turns the behavior on.
:disable    A function of no variables, which turns the behavior off.
:commands   A list of interactive commands that can be invoked in
            conjunction with the behavior.  These will appear in a submenu
            along with the rest of the items for the behavior.

Behaviors are assumed to be global, and to take effect immediately; if
the underlying package is per-buffer, it may have to scan all existing
buffers and frob them.  When a behavior is disabled, it should completely
go away *everywhere*, as if it were never invoked at all.

The :disable keyword can be missing, although this is considered bad
practice.  In such a case, attempting to disable the behavior will signal
an error unless you use the `force' option.

The :enable keyword can be missing.  This is useful for behaviors that
are really a series of related commands without anything semantically
corresponding to \"turning on\" or \"turning off\" the behavior.

A behavior with no :enable and no :command is possible.  This might be
used, for example, by a behavior that encapsulates a series of related
Lisp functions.  Such behaviors may be handled specially, e.g. not
displayed in the menus or displayed in a separate location, since they
have no user-invocable behavior."
  (let ((entry (list :group (check-behavior-group group)
		     :custom-group custom-group
		     :short-doc short-doc :require require
		     :enable enable :disable disable
		     :commands commands)))
    (puthash name entry behavior-hash-table))
  ;; update the children list of the group we're in (maybe nil).
  (unless (member name (getf (gethash group behavior-group-hash-table)
			     :children))
    (push name (getf (gethash group behavior-group-hash-table) :children))))

(defun* override-behavior (name &key
			   short-doc
			   group
			   include
			   demote-others)
  "Override the default properties of a behavior group NAME.
Normally, groups are created and assigned properties by individual packages.
The resulting hierarchy may not make much sense globally.  This function
allows the hierarchy and appearance of a group to be specified globally,
and will take precendence over the properties assigned by `define-behavior-group'.  This allows a global organization to be imposed on groups, while still allowing for graceful handling of new or unknown groups.

NAME can be a symbol specifying a group name, or a list of
\(PARENT [...] NAME), where a path from a particular parent is explicitly
given. (This latter form allows the same name to be assigned to more than one
group.)

Accepted keywords are

:short-doc  A \"pretty\" version of the name, for use in menus.
:group      Parent group, if any.  Should not be given if the parents are
            explicitly specified in NAME.
:include    A list of behaviors that are specifically included in this
            group, in addition to those that are included by the behaviors
            themselves.
:demote-others If non-nil, exclude all behaviors not specified in the :include
            list and put them instead (i.e. \"demote\" them) to another group,
            usually a subgroup."
  (let ((entry (list :group (check-behavior-group group)
		     :short-doc short-doc
		     :include include
		     :demote-others demote-others)))
    (puthash name entry behavior-override-hash-table)))

(defun* define-behavior-group (name &key
			       (short-doc
				(capitalize-string-as-title
				 (replace-in-string (symbol-name name) "-"
						    " ")))
			       group)
  "Define a behavior group NAME.

NAME can be a symbol specifying a group name, or a list of
\(PARENT [...] NAME), where a path from a particular parent is explicitly
given. (This latter form allows the same name to be assigned to more than one
group.)

Accepted keywords are

:short-doc  A \"pretty\" version of the name, for use in menus.  If omitted
              a prettified name will be generated.
:group      Parent group, if any.  Should not be given if the parents are
            explicitly specified in NAME."
  (let ((entry (list :group (check-behavior-group group)
		     :short-doc short-doc)))
    (puthash name entry behavior-group-hash-table))
  ;; update the children list of the parent (maybe nil).
  (push name (getf (gethash group behavior-group-hash-table) :children)))

(defun read-behavior (prompt &optional must-match initial-contents history
		      default-value)
  "Return a behavior symbol from the minibuffer, prompting with string PROMPT.
If non-nil, optional second arg INITIAL-CONTENTS is a string to insert
 in the minibuffer before reading.
Third arg HISTORY, if non-nil, specifies a history list. (It defaults to
`behavior-history'.)
Fourth arg DEFAULT-VALUE is the default value.  If non-nil, it is used
 for history command, and as the value to return if the user enters the
 empty string."
  (let ((result
	 (completing-read
	  prompt
	  (let (list)
	    (maphash #'(lambda (key value)
			 (push (cons (symbol-name key) value) list))
		     behavior-hash-table)
	    list)
	  nil must-match initial-contents (or history 'behavior-history)
	  default-value)))
    (if (and result (stringp result))
	(intern result)
      result)))

(defun behavior-enabled-p (behavior)
  "Non-nil if BEHAVIOR (a symbol) if currently enabled."
  (memq behavior enabled-behavior-list))

(defun enable-behavior (behavior &optional force)
  "Enable the specified behavior."
  (interactive (list (read-behavior "Enable Behavior: " t) current-prefix-arg))
  (let ((plist (gethash behavior behavior-hash-table)))
    (or plist (error 'invalid-argument "Not a behavior" behavior))
    (or force (not (memq behavior enabled-behavior-list))
	(error 'invalid-change "Behavior already enabled" behavior))
    (let ((require (getf plist :require))
	  (enable (getf plist :enable)))
      (cond ((listp require)
	     (mapc #'(lambda (sym) (require sym)) require))
	    ((symbolp require)
	     (require require))
	    ((null require))
	    (t (error 'invalid-argument "Invalid :require spec" require)))
      (message "Enabling behavior %s..." behavior)
      (if enable (funcall enable))
      (message "Enabling behavior %s...done" behavior)
      (let ((within-behavior-enabling-disabling t))
	(customize-set-variable 'enabled-behavior-list
				(cons behavior enabled-behavior-list))))))

(defun disable-behavior (behavior &optional force)
  "Disable the specified behavior."
  (interactive (list (read-behavior "Disable Behavior: " t)
		     current-prefix-arg))
  (let ((plist (gethash behavior behavior-hash-table)))
    (or plist (error 'invalid-argument "Not a behavior" behavior))
    (or force (memq behavior enabled-behavior-list)
	(error 'invalid-change "Behavior not enabled" behavior))
    (let ((require (getf plist :require))
	  (disable (getf plist :disable)))
      (cond ((listp require)
	     (mapc #'(lambda (sym) (require sym)) require))
	    ((symbolp require)
	     (require require))
	    ((null require))
	    (t (error 'invalid-argument "Invalid :require spec" require)))
      (message "Disabling behavior %s..." behavior)
      (if disable (funcall disable))
      (message "Disabling behavior %s...done" behavior)
      (let ((within-behavior-enabling-disabling t))
	(customize-set-variable 'enabled-behavior-list
				(delq behavior enabled-behavior-list))))))

(defun compute-behavior-group-children (group hash)
  "Compute the actual children for GROUP and its subgroups.
This takes into account the override information specified."
  (let* ((group-plist (gethash group behavior-group-hash-table))
	 (override (gethash group behavior-override-hash-table))
	 (children (getf group-plist :children)))
    )
  )

(defun behavior-menu-filter-1 (menu group)
  (submenu-generate-accelerator-spec
   (let* (
	  ;;options
	  ;;help
	  (enable
	   (menu-split-long-menu
	    (menu-sort-menu
	     (let ((group-plist (gethash group behavior-group-hash-table)))
	       (loop for behavior in (getf group-plist :children)
		 nconc (if (behavior-group-p behavior)
			   (list
			    (cons (getf
				   (gethash behavior behavior-group-hash-table)
				   :short-doc)
				  (behavior-menu-filter-1 menu behavior)))
			 (let* ((plist (gethash behavior behavior-hash-table))
				(commands (getf plist :commands)))
			   (nconc
			    (if (getf plist :enable)
				`([,(format "%s (%s) [toggle]"
					    (getf plist :short-doc)
					    behavior)
				   (if (memq ',behavior
					     enabled-behavior-list)
				       (disable-behavior ',behavior)
				     (enable-behavior ',behavior))
				   :active ,(if (getf plist :disable) t
					      (not (memq
						    ',behavior
						    enabled-behavior-list)))
				   :style toggle
				   :selected (memq ',behavior
						   enabled-behavior-list)]))
			    (cond ((null commands) nil)
				  ((and (eq (length commands) 1)
					(vectorp (elt commands 0)))
				   (let ((comm (copy-sequence
						(elt commands 0))))
				     (setf (elt comm 0)
					   (format "%s (%s)"
						   (elt comm 0) behavior))
				     (list comm)))
				  (t (list
				      (cons (format "%s (%s) Commands"
						    (getf plist :short-doc)
						    behavior)
					    commands)))))))))
	     ))
	   )
	  )
     enable)
   '(?p)))

(defun behavior-menu-filter (menu)
  (append
   `(("%_Package Utilities"
       ("%_Set Download Site"
	("%_Official Releases"
	 :filter ,#'(lambda (&rest junk)
                    (menu-split-long-menu
                     (submenu-generate-accelerator-spec
                      (package-ui-download-menu)))))
	("%_Pre-Releases"
	 :filter ,#'(lambda (&rest junk)
                      (menu-split-long-menu
                       (submenu-generate-accelerator-spec
                        (package-ui-pre-release-download-menu)))))
	("%_Site Releases"
	 :filter ,#'(lambda (&rest junk)
                      (menu-split-long-menu
                       (submenu-generate-accelerator-spec
                        (package-ui-site-release-download-menu))))))
       "--:shadowEtchedIn"
      ["%_Update Package Index" package-get-update-base]
      ["%_List and Install" pui-list-packages]
      ["U%_pdate Installed Packages" package-get-update-all]
      ["%_Help" (Info-goto-node "(xemacs)Packages")])
     "----")
   (behavior-menu-filter-1 menu nil)))

;; Initialize top-level group.
(puthash nil '(:children nil :short-doc "Root") behavior-group-hash-table)

(provide 'behavior)

;;; finder-inf.el ends here