New major mode "SES" for spreadsheets.

New function (unsafep X) determines whether X is a safe Lisp form.
New support module testcover.el for coverage testing.
This commit is contained in:
Jonathan Yavner 2002-09-28 18:45:56 +00:00
parent 6209bd8c0a
commit 7ed9159a5c
16 changed files with 5722 additions and 24 deletions

View file

@ -1,3 +1,7 @@
2002-09-16 Jonathan Yavner <jyavner@engineer.com>
* ses-example.ses: New file: example spreadsheet.
2002-09-04 Kenichi Handa <handa@etl.go.jp>
* HELLO: Fix Unicode Greek line.

207
etc/ses-example.ses Normal file
View file

@ -0,0 +1,207 @@
Sales summary - Acme fundraising
~~~~~~~~~~~~~Summary~~~~~~~~~~~~~
--Totals-- Average
Eastern-area $46.70 2 $23.35
West-district $80.25 10 $8.03
North&South $99.69 5 $19.94
TOTAL ~$227 17 $13.33
= = = = = =Details = = = = = =
99/07/25 North&South $40.00
99/08/16 West-district $5.25
99/08/16 North&South $12.99
99/08/25 West-district $8.61
99/08/26 West-district $9.97
99/09/04 Eastern-area $21.00
00/01/15 West-district $5.50
00/07/15 West-district $19.01
00/07/26 North&South $27.95
00/08/04 West-district $11.71
00/08/16 Eastern-area $25.70
00/08/25 West-district $4.95
00/08/26 West-district $7.21
00/09/01 North&South $1.25
01/07/25 West-district $5.75
01/08/04 West-district $2.29
01/08/15 North&South $17.50
(ses-cell A1 "Sales summary - Acme fundraising" "Sales summary - Acme fundraising" nil nil)
(ses-cell B1 *skip* nil nil nil)
(ses-cell C1 *skip* nil nil nil)
(ses-cell D1 *skip* nil nil nil)
(ses-cell E1 nil nil nil nil)
(ses-cell A2 nil nil nil nil)
(ses-cell B2 nil nil nil nil)
(ses-cell C2 nil nil nil nil)
(ses-cell D2 nil nil nil nil)
(ses-cell E2 nil nil nil nil)
(ses-cell A3 nil nil nil nil)
(ses-cell B3 "Summary" "Summary" ses-tildefill-span nil)
(ses-cell C3 *skip* nil nil nil)
(ses-cell D3 *skip* nil nil nil)
(ses-cell E3 *skip* nil nil nil)
(ses-cell A4 nil nil nil nil)
(ses-cell B4 nil nil nil nil)
(ses-cell C4 "Totals" "Totals" ses-dashfill-span nil)
(ses-cell D4 *skip* nil nil nil)
(ses-cell E4 "Average" "Average" nil nil)
(ses-cell A5 nil nil nil nil)
(ses-cell B5 Eastern-area (quote Eastern-area) nil nil)
(ses-cell C5 46.7 (apply (quote +) (ses-select (ses-range B11 B27) (quote Eastern-area) (ses-range C11 C27))) nil (C8 E5))
(ses-cell D5 2 (length (ses-select (ses-range B11 B27) (quote Eastern-area) (ses-range C11 C27))) nil (D8 E5))
(ses-cell E5 23.35 (/ C5 D5) nil nil)
(ses-cell A6 nil nil nil nil)
(ses-cell B6 West-district (quote West-district) nil nil)
(ses-cell C6 80.25 (apply (quote +) (ses-select (ses-range B11 B27) (quote West-district) (ses-range C11 C27))) nil (C8 E6))
(ses-cell D6 10 (length (ses-select (ses-range B11 B27) (quote West-district) (ses-range C11 C27))) nil (D8 E6))
(ses-cell E6 8.025 (/ C6 D6) nil nil)
(ses-cell A7 nil nil nil nil)
(ses-cell B7 North&South (quote North&South) nil nil)
(ses-cell C7 99.69 (apply (quote +) (ses-select (ses-range B11 B27) (quote North&South) (ses-range C11 C27))) nil (C8 E7))
(ses-cell D7 5 (length (ses-select (ses-range B11 B27) (quote North&South) (ses-range C11 C27))) nil (D8 E7))
(ses-cell E7 19.938 (/ C7 D7) nil nil)
(ses-cell A8 nil nil nil nil)
(ses-cell B8 "TOTAL" "TOTAL" nil nil)
(ses-cell C8 226.64 (ses+ C5 C6 C7) "~$%.0f" (E8))
(ses-cell D8 17 (ses+ D5 D6 D7) nil (E8))
(ses-cell E8 13.331764705882351 (/ C8 D8) nil nil)
(ses-cell A9 nil nil ses-center nil)
(ses-cell B9 useless (quote useless) (lambda (x) (if (eq x (quote useless)) "" (prin1-to-string x))) nil)
(ses-cell C9 nil nil nil nil)
(ses-cell D9 nil nil nil nil)
(ses-cell E9 nil nil nil nil)
(ses-cell A10 "Details " "Details " (lambda (x) (replace-regexp-in-string "==" "= " (ses-center-span x 61))) nil)
(ses-cell B10 *skip* nil nil nil)
(ses-cell C10 *skip* nil nil nil)
(ses-cell D10 "" "" nil nil)
(ses-cell E10 nil nil nil nil)
(ses-cell A11 990725 990725 nil nil)
(ses-cell B11 North&South (quote North&South) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C11 40 40 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D11 nil nil nil nil)
(ses-cell E11 nil nil nil nil)
(ses-cell A12 990816 990816 nil nil)
(ses-cell B12 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C12 5.25 5.25 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D12 nil nil nil nil)
(ses-cell E12 nil nil nil nil)
(ses-cell A13 990816 990816 nil nil)
(ses-cell B13 North&South (quote North&South) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C13 12.99 12.99 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D13 nil nil nil nil)
(ses-cell E13 nil nil nil nil)
(ses-cell A14 990825 990825 nil nil)
(ses-cell B14 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C14 8.61 8.61 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D14 nil nil nil nil)
(ses-cell E14 nil nil nil nil)
(ses-cell A15 990826 990826 nil nil)
(ses-cell B15 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C15 9.97 9.97 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D15 nil nil nil nil)
(ses-cell E15 nil nil nil nil)
(ses-cell A16 990904 990904 nil nil)
(ses-cell B16 Eastern-area (quote Eastern-area) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C16 21 (/ life-universe-everything 2) nil (D7 C7 D6 C6 D5 C5))
(ses-cell D16 nil nil nil nil)
(ses-cell E16 nil nil nil nil)
(ses-cell A17 115 115 nil nil)
(ses-cell B17 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C17 5.5 5.5 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D17 nil nil nil nil)
(ses-cell E17 nil nil nil nil)
(ses-cell A18 715 715 nil nil)
(ses-cell B18 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C18 19.01 19.01 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D18 nil nil nil nil)
(ses-cell E18 nil nil nil nil)
(ses-cell A19 726 726 nil nil)
(ses-cell B19 North&South (quote North&South) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C19 27.95 27.95 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D19 nil nil nil nil)
(ses-cell E19 nil nil nil nil)
(ses-cell A20 804 804 nil nil)
(ses-cell B20 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C20 11.71 11.71 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D20 nil nil nil nil)
(ses-cell E20 nil nil nil nil)
(ses-cell A21 816 816 nil nil)
(ses-cell B21 Eastern-area (quote Eastern-area) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C21 25.7 25.7 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D21 nil nil nil nil)
(ses-cell E21 nil nil nil nil)
(ses-cell A22 825 825 nil nil)
(ses-cell B22 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C22 4.95 4.95 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D22 nil nil nil nil)
(ses-cell E22 nil nil nil nil)
(ses-cell A23 826 826 nil nil)
(ses-cell B23 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C23 7.21 7.21 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D23 nil nil nil nil)
(ses-cell E23 nil nil nil nil)
(ses-cell A24 901 901 nil nil)
(ses-cell B24 North&South (quote North&South) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C24 1.25 1.25 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D24 nil nil nil nil)
(ses-cell E24 nil nil nil nil)
(ses-cell A25 10725 10725 nil nil)
(ses-cell B25 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C25 5.75 5.75 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D25 nil nil nil nil)
(ses-cell E25 nil nil nil nil)
(ses-cell A26 10804 10804 nil nil)
(ses-cell B26 West-district (quote West-district) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C26 2.29 2.29 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D26 nil nil nil nil)
(ses-cell E26 nil nil nil nil)
(ses-cell A27 10815 10815 nil nil)
(ses-cell B27 North&South (quote North&South) nil (D7 C7 D6 C6 D5 C5))
(ses-cell C27 17.5 17.5 nil (D7 C7 D6 C6 D5 C5))
(ses-cell D27 nil nil nil nil)
(ses-cell E27 nil nil nil nil)
(ses-column-widths [8 14 6 3 7])
(ses-column-printers [(lambda (x) (format "%02d/%02d/%02d" (/ x 10000) (% (/ x 100) 100) (% x 100))) ("%s") "$%.2f" nil "$%.2f"])
(ses-default-printer "%.7g")
(ses-header-row 0)
( ;Global parameters (these are read first)
2 ;SES file-format
27 ;numrows
5 ;numcols
)
;;; Local Variables:
;;; mode: ses
;;; life-universe-everything: 42
;;; symbolic-formulas: (("Eastern area") ("West-district") ("North&South") ("Other"))
;;; End:

View file

@ -895,6 +895,17 @@
* viper.el (viper-emacs-state-mode-list): Added modes.
2002-09-18 Jonathan Yavner <jyavner@engineer.com>
* emacs-lisp/testcover.el: New file. Uses edebug to instrument a
module of code, with graphical display of poor-coverage spots.
* emacs-lisp/testcover-ses.el: New file. Demonstrates use of
testcover on a interactive module like ses.
* emacs-lisp/testcover-unsafep.el: New file. Demonstrates use of
testcover on a noninteractive module like unsafep.
2002-09-18 Miles Bader <miles@gnu.org>
* diff-mode.el (diff-mode): Don't evaluate `compilation-last-buffer'
@ -909,6 +920,21 @@
Don't output the C-x # message if `nowait'.
(server-buffer-done): Use server-log's new arg.
2002-09-16 Jonathan Yavner <jyavner@engineer.com>
* ses.el: New file.
* emacs-lisp/unsafep.el: New file.
* files.el (auto-mode-alist): Add ".ses" for ses-mode.
(inhibit-quit): This is risky for unsafep, doesn't matter much for
anybody else.
(risky-local-variable-p): New function. Split off from
hack-one-local-variable so unsafep can use it. Add \|-history$ to
the list of disallowed local variable names (malicious user could
stuff a `display' property in there that would be activated when
na,Ao(Bve user called up the history).
2002-09-16 Markus Rost <rost@math.ohio-state.edu>
* ls-lisp.el (ls-lisp-format-time-list): Fix type and provide :tag's.

View file

@ -37,8 +37,8 @@
(put 'ps-print-vertical 'custom-loads '("ps-print"))
(put 'supercite-hooks 'custom-loads '("supercite"))
(put 'vhdl-menu 'custom-loads '("vhdl-mode"))
(put 'chinese-calendar 'custom-loads '("cal-china"))
(put 'gnus-newsrc 'custom-loads '("gnus-start"))
(put 'chinese-calendar 'custom-loads '("cal-china"))
(put 'expand 'custom-loads '("expand"))
(put 'bookmark 'custom-loads '("bookmark"))
(put 'icon 'custom-loads '("icon"))
@ -221,8 +221,8 @@
(put 'auto-save 'custom-loads '("files" "startup"))
(put 'tpu 'custom-loads '("tpu-edt" "tpu-extras"))
(put 'w32 'custom-loads '("w32-vars"))
(put 'viper-hooks 'custom-loads '("viper-init"))
(put 'gnus-cite 'custom-loads '("gnus-cite"))
(put 'viper-hooks 'custom-loads '("viper-init"))
(put 'gnus-demon 'custom-loads '("gnus-demon"))
(put 'reftex-optimizations-for-large-documents 'custom-loads '("reftex-vars"))
(put 'viper-misc 'custom-loads '("viper-cmd" "viper-init" "viper"))
@ -265,18 +265,20 @@
(put 'ps-print 'custom-loads '("ps-print"))
(put 'view 'custom-loads '("view" "calendar"))
(put 'cwarn 'custom-loads '("cwarn"))
(put 'testcover 'custom-loads '("testcover"))
(put 'gnus-score-default 'custom-loads '("gnus-score" "gnus-sum"))
(put 'ebnf-except 'custom-loads '("ebnf2ps"))
(put 'nnmail-duplicate 'custom-loads '("nnmail"))
(put 'handwrite 'custom-loads '("handwrite"))
(put 'tags 'custom-loads '("speedbar"))
(put 'ses 'custom-loads '("ses"))
(put 'eshell-proc 'custom-loads '("esh-proc"))
(put 'custom-browse 'custom-loads '("cus-edit"))
(put 'mime 'custom-loads '("mailcap" "mm-bodies"))
(put 'generic-x 'custom-loads '("generic-x"))
(put 'partial-completion 'custom-loads '("complete"))
(put 'whitespace 'custom-loads '("whitespace"))
(put 'maint 'custom-loads '("emacsbug" "gulp" "lisp-mnt"))
(put 'maint 'custom-loads '("gulp" "lisp-mnt" "emacsbug"))
(put 'pages 'custom-loads '("page-ext"))
(put 'message-interface 'custom-loads '("message"))
(put 'diary 'custom-loads '("calendar" "diary-lib" "solar"))
@ -374,8 +376,8 @@
(put 'log-view 'custom-loads '("log-view"))
(put 'PostScript 'custom-loads '("ps-mode"))
(put 'abbrev-mode 'custom-loads '("abbrev" "cus-edit" "mailabbrev"))
(put 'eshell-term 'custom-loads '("em-term"))
(put 'earcon 'custom-loads '("earcon"))
(put 'eshell-term 'custom-loads '("em-term"))
(put 'feedmail-headers 'custom-loads '("feedmail"))
(put 'hypermedia 'custom-loads '("wid-edit" "metamail" "browse-url" "goto-addr"))
(put 'image 'custom-loads '("image-file"))
@ -466,14 +468,14 @@
(put 'bibtex 'custom-loads '("bibtex"))
(put 'faces 'custom-loads '("faces" "loaddefs" "facemenu" "cus-edit" "font-lock" "hilit-chg" "paren" "ps-print" "speedbar" "time" "whitespace" "wid-edit" "woman" "gnus" "message" "cwarn" "make-mode"))
(put 'gnus-summary-various 'custom-loads '("gnus-sum"))
(put 'applications 'custom-loads '("calendar" "cus-edit" "uniquify" "eshell" "spell"))
(put 'applications 'custom-loads '("calendar" "cus-edit" "ses" "uniquify" "eshell" "spell"))
(put 'ebrowse-member 'custom-loads '("ebrowse"))
(put 'terminal 'custom-loads '("terminal"))
(put 'shadow 'custom-loads '("shadowfile" "shadow"))
(put 'hl-line 'custom-loads '("hl-line"))
(put 'eshell-glob 'custom-loads '("em-glob"))
(put 'internal 'custom-loads '("startup" "cus-edit" "delim-col"))
(put 'lisp 'custom-loads '("simple" "lisp" "lisp-mode" "ielm" "xscheme" "advice" "bytecomp" "checkdoc" "cl-indent" "cust-print" "edebug" "eldoc" "elp" "find-func" "pp" "re-builder" "shadow" "trace" "scheme"))
(put 'lisp 'custom-loads '("simple" "lisp" "lisp-mode" "ielm" "unsafep" "xscheme" "advice" "bytecomp" "checkdoc" "cl-indent" "cust-print" "edebug" "eldoc" "elp" "find-func" "pp" "re-builder" "shadow" "testcover" "trace" "scheme"))
(put 'local 'custom-loads '("calendar"))
(put 'rlogin 'custom-loads '("rlogin"))
(put 'debugger 'custom-loads '("debug"))
@ -848,10 +850,14 @@ as a PDF file <URL:http://www.ecma.ch/ecma1/STAND/ECMA-048.HTM>.")
(custom-put-if-not 'sql-db2-options 'standard-value t)
(custom-put-if-not 'cwarn 'custom-version "21.1")
(custom-put-if-not 'cwarn 'group-documentation "Highlight suspicious C and C++ constructions.")
(custom-put-if-not 'testcover 'custom-version "21.1")
(custom-put-if-not 'testcover 'group-documentation "Code-coverage tester")
(custom-put-if-not 'sgml-xml-mode 'custom-version "21.4")
(custom-put-if-not 'sgml-xml-mode 'standard-value t)
(custom-put-if-not 'message-buffer-naming-style 'custom-version "21.1")
(custom-put-if-not 'message-buffer-naming-style 'standard-value t)
(custom-put-if-not 'ses 'custom-version "21.1")
(custom-put-if-not 'ses 'group-documentation "Simple Emacs Spreadsheet")
(custom-put-if-not 'ps-footer-font-size 'custom-version "21.1")
(custom-put-if-not 'ps-footer-font-size 'standard-value t)
(custom-put-if-not 'hscroll-margin 'custom-version "21.3")
@ -872,10 +878,10 @@ as a PDF file <URL:http://www.ecma.ch/ecma1/STAND/ECMA-048.HTM>.")
(custom-put-if-not 'vc-diff-switches 'standard-value t)
(custom-put-if-not 'vcursor-interpret-input 'custom-version "20.3")
(custom-put-if-not 'vcursor-interpret-input 'standard-value t)
(custom-put-if-not 'diary-sabbath-candles-minutes 'custom-version "21.1")
(custom-put-if-not 'diary-sabbath-candles-minutes 'standard-value t)
(custom-put-if-not 'gnus-audio 'custom-version "21.1")
(custom-put-if-not 'gnus-audio 'group-documentation "Playing sound in Gnus.")
(custom-put-if-not 'diary-sabbath-candles-minutes 'custom-version "21.1")
(custom-put-if-not 'diary-sabbath-candles-minutes 'standard-value t)
(custom-put-if-not 'trailing-whitespace 'custom-version "21.1")
(custom-put-if-not 'trailing-whitespace 'group-documentation nil)
(custom-put-if-not 'fortran-comment-line-start 'custom-version "21.1")

View file

@ -0,0 +1,711 @@
;;;; testcover-ses.el -- Example use of `testcover' to test "SES"
;; Copyright (C) 2002 Free Software Foundation, Inc.
;; Author: Jonathan Yavner <jyavner@engineer.com>
;; Maintainer: Jonathan Yavner <jyavner@engineer.com>
;; Keywords: spreadsheet lisp utility
;; GNU Emacs 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.
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
(require 'testcover)
;;;Here are some macros that exercise SES. Set `pause' to t if you want the
;;;macros to pause after each step.
(let* ((pause nil)
(x (if pause "q" ""))
(y "ses-test.ses\r<"))
;;Fiddle with the existing spreadsheet
(fset 'ses-exercise-example
(concat "" data-directory "ses-example.ses\r<"
x "10"
x " "
x ""
x "pses-center\r"
x "p\r"
x "\t\t"
x "\r A9 B9\r"
x ""
x "\r 2\r"
x ""
x "50\r"
x "4"
x " "
x ""
x "(+ o\0"
x "-1o \r"
x ""
x))
;;Create a new spreadsheet
(fset 'ses-exercise-new
(concat y
x "\"%.8g\"\r"
x "2\r"
x ""
x ""
x "2"
x "\"Header\r"
x "(sqrt 1\r"
x "pses-center\r"
x "\t"
x "(+ A2 A3\r"
x "(* B2 A3\r"
x "2"
x "\rB3\r"
x ""
x))
;;Basic cell display
(fset 'ses-exercise-display
(concat y ":(revert-buffer t t)\r"
x ""
x "\"Very long\r"
x "w3\r"
x "w3\r"
x "(/ 1 0\r"
x "234567\r"
x "5w"
x "\t1\r"
x ""
x "234567\r"
x "\t"
x ""
x "345678\r"
x "3w"
x "\0>"
x ""
x ""
x ""
x ""
x ""
x ""
x ""
x "1\r"
x ""
x ""
x "\"1234567-1234567-1234567\r"
x "123\r"
x "2"
x "\"1234567-1234567-1234567\r"
x "123\r"
x "w8\r"
x "\"1234567\r"
x "w5\r"
x))
;;Cell formulas
(fset 'ses-exercise-formulas
(concat y ":(revert-buffer t t)\r"
x "\t\t"
x "\t"
x "(* B1 B2 D1\r"
x "(* B2 B3\r"
x "(apply '+ (ses-range B1 B3)\r"
x "(apply 'ses+ (ses-range B1 B3)\r"
x "(apply 'ses+ (ses-range A2 A3)\r"
x "(mapconcat'number-to-string(ses-range B2 B4) \"-\"\r"
x "(apply 'concat (reverse (ses-range A3 D3))\r"
x "(* (+ A2 A3) (ses+ B2 B3)\r"
x ""
x "2"
x "5\t"
x "(apply 'ses+ (ses-range E1 E2)\r"
x "(apply 'ses+ (ses-range A5 B5)\r"
x "(apply 'ses+ (ses-range E1 F1)\r"
x "(apply 'ses+ (ses-range D1 E1)\r"
x "\t"
x "(ses-average (ses-range A2 A5)\r"
x "(apply 'ses+ (ses-range A5 A6)\r"
x "k"
x " "
x ""
x "2"
x "3 "
x "o"
x "2o"
x "3k"
x "(ses-average (ses-range B3 E3)\r"
x "k"
x "12345678\r"
x))
;;Recalculating and reconstructing
(fset 'ses-exercise-recalc
(concat y ":(revert-buffer t t)\r"
x " "
x "\t\t"
x ""
x "(/ 1 0\r"
x ""
x "\n"
x ""
x "\"%.6g\"\r"
x " "
x ">nw"
x "\0>xdelete-region\r"
x " "
x "8"
x "\0>xdelete-region\r"
x " "
x ""
x " k"
x " "
x "\"Very long\r"
x ""
x "\r\r"
x ""
x "o"
x ""
x "\"Very long2\r"
x "o"
x ""
x "\rC3\r"
x "\rC2\r"
x "\0"
x "\rC4\r"
x "\rC2\r"
x "\0"
x ""
x "xses-mode\r"
x "<"
x "2k"
x))
;;Header line
(fset 'ses-exercise-header-row
(concat y ":(revert-buffer t t)\r"
x "<"
x ">"
x "6<"
x ">"
x "7<"
x ">"
x "8<"
x "2<"
x ">"
x "3w"
x "10<"
x ">"
x "2 "
x))
;;Detecting unsafe formulas and printers
(fset 'ses-exercise-unsafe
(concat y ":(revert-buffer t t)\r"
x "p(lambda (x) (delete-file x))\rn"
x "p(lambda (x) (delete-file \"ses-nothing\"))\ry"
x "\0n"
x "(delete-file \"x\"\rn"
x "(delete-file \"ses-nothing\"\ry"
x "\0n"
x "(open-network-stream \"x\" nil \"localhost\" \"smtp\"\ry"
x "\0n"
x))
;;Inserting and deleting rows
(fset 'ses-exercise-rows
(concat y ":(revert-buffer t t)\r"
x ""
x "\"%s=\"\r"
x "20"
x "p\"%s+\"\r"
x ""
x "123456789\r"
x "\021"
x ""
x " "
x "(not B25\r"
x "k"
x "jA3\r"
x "19 "
x " "
x "100" ;Make this approx your CPU speed in MHz
x))
;;Inserting and deleting columns
(fset 'ses-exercise-columns
(concat y ":(revert-buffer t t)\r"
x "\"%s@\"\r"
x "o"
x ""
x "o"
x " "
x "k"
x "w8\r"
x "p\"%.7s*\"\r"
x "o"
x ""
x "2o"
x "3k"
x "\"%.6g\"\r"
x "26o"
x "\026\t"
x "26o"
x "0\r"
x "26\t"
x "400"
x "50k"
x "\0D"
x))
(fset 'ses-exercise-editing
(concat y ":(revert-buffer t t)\r"
x "1\r"
x "('x\r"
x ""
x ""
x "\r\r"
x "w9\r"
x "\r.5\r"
x "\r 10\r"
x "w12\r"
x "\r'\r"
x "\r\r"
x "jA4\r"
x "(+ A2 100\r"
x "3\r"
x "jB1\r"
x "(not A1\r"
x "\"Very long\r"
x ""
x "h"
x "H"
x ""
x ">\t"
x ""
x ""
x "2"
x ""
x "o"
x "h"
x "\0"
x "\"Also very long\r"
x "H"
x "\0'\r"
x "'Trial\r"
x "'qwerty\r"
x "(concat o<\0"
x "-1o\r"
x "(apply '+ o<\0-1o\r"
x "2"
x "-2"
x "-2"
x "2"
x " "
x "H"
x "\0"
x "\"Another long one\r"
x "H"
x ""
x "<"
x ""
x ">"
x "\0"
x))
;;Sorting of columns
(fset 'ses-exercise-sort-column
(concat y ":(revert-buffer t t)\r"
x "\"Very long\r"
x "99\r"
x "o13\r"
x "(+ A3 B3\r"
x "7\r8\r(* A4 B4\r"
x "\0A\r"
x "\0B\r"
x "\0C\r"
x "o"
x "\0C\r"
x))
;;Simple cell printers
(fset 'ses-exercise-cell-printers
(concat y ":(revert-buffer t t)\r"
x "\"4\t76\r"
x "\"4\n7\r"
x "p\"{%S}\"\r"
x "p(\"[%s]\")\r"
x "p(\"<%s>\")\r"
x "\0"
x "p\r"
x "pnil\r"
x "pses-dashfill\r"
x "48\r"
x "\t"
x "\0p\r"
x "p\r"
x "pses-dashfill\r"
x "\0pnil\r"
x "5\r"
x "pses-center\r"
x "\"%s\"\r"
x "w8\r"
x "p\r"
x "p\"%.7g@\"\r"
x "\r"
x "\"%.6g#\"\r"
x "\"%.6g.\"\r"
x "\"%.6g.\"\r"
x "pidentity\r"
x "6\r"
x "\"UPCASE\r"
x "pdowncase\r"
x "(* 3 4\r"
x "p(lambda (x) '(\"Hi\"))\r"
x "p(lambda (x) '(\"Bye\"))\r"
x))
;;Spanning cell printers
(fset 'ses-exercise-spanning-printers
(concat y ":(revert-buffer t t)\r"
x "p\"%.6g*\"\r"
x "pses-dashfill-span\r"
x "5\r"
x "pses-tildefill-span\r"
x "\"4\r"
x "p\"$%s\"\r"
x "p(\"$%s\")\r"
x "8\r"
x "p(\"!%s!\")\r"
x "\t\"12345678\r"
x "pses-dashfill-span\r"
x "\"23456789\r"
x "\t"
x "(not t\r"
x "w6\r"
x "\"5\r"
x "o"
x "k"
x "k"
x "\t"
x ""
x "o"
x "2k"
x "k"
x))
;;Cut/copy/paste - within same buffer
(fset 'ses-exercise-paste-1buf
(concat y ":(revert-buffer t t)\r"
x "\0w"
x ""
x "o"
x "\"middle\r"
x "\0"
x "w"
x "\0"
x "w"
x ""
x ""
x "2y"
x "y"
x "y"
x ">"
x "y"
x ">y"
x "<"
x "p\"<%s>\"\r"
x "pses-dashfill\r"
x "\0"
x ""
x ""
x "y"
x "\r\0w"
x "\r"
x "3(+ G2 H1\r"
x "\0w"
x ">"
x ""
x "8(ses-average (ses-range G2 H2)\r"
x "\0k"
x "7"
x ""
x "(ses-average (ses-range E7 E9)\r"
x "\0 "
x ""
x "(ses-average (ses-range E7 F7)\r"
x "\0k"
x ""
x "(ses-average (ses-range D6 E6)\r"
x "\0k"
x ""
x "2"
x "\"Line A\r"
x "pses-tildefill-span\r"
x "\"Subline A(1)\r"
x "pses-dashfill-span\r"
x "\0w"
x ""
x ""
x "\0w"
x ""
x))
;;Cut/copy/paste - between two buffers
(fset 'ses-exercise-paste-2buf
(concat y ":(revert-buffer t t)\r"
x "o\"middle\r\0"
x ""
x "4bses-test.txt\r"
x " "
x "\"xxx\0"
x "wo"
x ""
x ""
x "o\"\0"
x "wo"
x "o123.45\0"
x "o"
x "o1 \0"
x "o"
x ">y"
x "o symb\0"
x "oy2y"
x "o1\t\0"
x "o"
x "w9\np\"<%s>\"\n"
x "o\n2\t\"3\nxxx\t5\n\0"
x "oy"
x))
;;Export text, import it back
(fset 'ses-exercise-import-export
(concat y ":(revert-buffer t t)\r"
x "\0xt"
x "4bses-test.txt\r"
x "\n-1o"
x "xTo-1o"
x "'crunch\r"
x "pses-center-span\r"
x "\0xT"
x "o\n-1o"
x "\0y"
x "\0xt"
x "\0y"
x "12345678\r"
x "'bunch\r"
x "\0xtxT"
x)))
(defun ses-exercise-macros ()
"Executes all SES coverage-test macros."
(dolist (x '(ses-exercise-example
ses-exercise-new
ses-exercise-display
ses-exercise-formulas
ses-exercise-recalc
ses-exercise-header-row
ses-exercise-unsafe
ses-exercise-rows
ses-exercise-columns
ses-exercise-editing
ses-exercise-sort-column
ses-exercise-cell-printers
ses-exercise-spanning-printers
ses-exercise-paste-1buf
ses-exercise-paste-2buf
ses-exercise-import-export))
(message "<Testing %s>" x)
(execute-kbd-macro x)))
(defun ses-exercise-signals ()
"Exercise code paths that lead to error signals, other than those for
spreadsheet files with invalid formatting."
(message "<Checking for expected errors>")
(switch-to-buffer "ses-test.ses")
(deactivate-mark)
(ses-jump 'A1)
(ses-set-curcell)
(dolist (x '((ses-column-widths 14)
(ses-column-printers "%s")
(ses-column-printers ["%s" "%s" "%s"]) ;Should be two
(ses-column-widths [14])
(ses-delete-column -99)
(ses-delete-column 2)
(ses-delete-row -1)
(ses-goto-data 'hogwash)
(ses-header-row -56)
(ses-header-row 99)
(ses-insert-column -14)
(ses-insert-row 0)
(ses-jump 'B8) ;Covered by preceding cell
(ses-printer-validate '("%s" t))
(ses-printer-validate '([47]))
(ses-read-header-row -1)
(ses-read-header-row 32767)
(ses-relocate-all 0 0 -1 1)
(ses-relocate-all 0 0 1 -1)
(ses-select (ses-range A1 A2) 'x (ses-range B1 B1))
(ses-set-cell 0 0 'hogwash nil)
(ses-set-column-width 0 0)
(ses-yank-cells #("a\nb"
0 1 (ses (A1 nil nil))
2 3 (ses (A3 nil nil)))
nil)
(ses-yank-cells #("ab"
0 1 (ses (A1 nil nil))
1 2 (ses (A2 nil nil)))
nil)
(ses-yank-pop nil)
(ses-yank-tsf "1\t2\n3" nil)
(let ((curcell nil)) (ses-check-curcell))
(let ((curcell 'A1)) (ses-check-curcell 'needrange))
(let ((curcell '(A1 . A2))) (ses-check-curcell 'end))
(let ((curcell '(A1 . A2))) (ses-sort-column "B"))
(let ((curcell '(C1 . D2))) (ses-sort-column "B"))
(execute-kbd-macro "jB10\n2")
(execute-kbd-macro [?j ?B ?9 ?\n ?C-@ ?C-f ?C-f cut])
(progn (kill-new "x") (execute-kbd-macro ">n"))
(execute-kbd-macro "\0w")))
(condition-case nil
(progn
(eval x)
(signal 'singularity-error nil)) ;Shouldn't get here
(singularity-error (error "No error from %s?" x))
(error nil)))
;;Test quit-handling in ses-update-cells. Cant' use `eval' here.
(let ((inhibit-quit t))
(setq quit-flag t)
(condition-case nil
(progn
(ses-update-cells '(A1))
(signal 'singularity-error nil))
(singularity-error (error "Quit failure in ses-update-cells"))
(error nil))
(setq quit-flag nil)))
(defun ses-exercise-invalid-spreadsheets ()
"Execute code paths that detect invalid spreadsheet files."
;;Detect invalid spreadsheets
(let ((p&d "\n\n \n(ses-cell A1 nil nil nil nil)\n\n")
(cw "(ses-column-widths [7])\n")
(cp "(ses-column-printers [ses-center])\n")
(dp "(ses-default-printer \"%.7g\")\n")
(hr "(ses-header-row 0)\n")
(p11 "(2 1 1)")
(igp ses-initial-global-parameters))
(dolist (x (list "(1)"
"(x 2 3)"
"(1 x 3)"
"(1 -1 0)"
"(1 2 x)"
"(1 2 -1)"
"(3 1 1)"
"\n\n (2 1 1)"
"\n\n \n(ses-cell)(2 1 1)"
"\n\n \n(x)\n(2 1 1)"
"\n\n\n \n(ses-cell A2)\n(2 2 2)"
"\n\n\n \n(ses-cell B1)\n(2 2 2)"
"\n\n \n(ses-cell A1 nil nil nil nil)\n(2 1 1)"
(concat p&d "(x)\n(x)\n(x)\n(x)\n" p11)
(concat p&d "(ses-column-widths)(x)\n(x)\n(x)\n" p11)
(concat p&d cw "(x)\n(x)\n(x)\n(2 1 1)")
(concat p&d cw "(ses-column-printers)(x)\n(x)\n" p11)
(concat p&d cw cp "(x)\n(x)\n" p11)
(concat p&d cw cp "(ses-default-printer)(x)\n" p11)
(concat p&d cw cp dp "(x)\n" p11)
(concat p&d cw cp dp "(ses-header-row)" p11)
(concat p&d cw cp dp hr p11)
(concat p&d cw cp dp "\n" hr igp)))
(condition-case nil
(with-temp-buffer
(insert x)
(ses-load)
(signal 'singularity-error nil)) ;Shouldn't get here
(singularity-error (error "%S is an invalid spreadsheet!" x))
(error nil)))))
(defun ses-exercise-startup ()
"Prepare for coverage tests"
;;Clean up from any previous runs
(condition-case nil (kill-buffer "ses-example.ses") (error nil))
(condition-case nil (kill-buffer "ses-test.ses") (error nil))
(condition-case nil (delete-file "ses-test.ses") (file-error nil))
(delete-other-windows) ;Needed for "\C-xo" in ses-exercise-editing
(setq ses-mode-map nil) ;Force rebuild
(testcover-unmark-all "ses.el")
;;Enable
(let ((testcover-1value-functions
;;forward-line always returns 0, for us.
;;remove-text-properties always returns t for us.
;;ses-recalculate-cell returns the same " " any time curcell is a cons
;;Macros ses-dorange and ses-dotimes-msg generate code that always
;; returns nil
(append '(forward-line remove-text-properties ses-recalculate-cell
ses-dorange ses-dotimes-msg)
testcover-1value-functions))
(testcover-constants
;;These maps get initialized, then never changed again
(append '(ses-mode-map ses-mode-print-map ses-mode-edit-map)
testcover-constants)))
(testcover-start "ses.el" t))
(require 'unsafep)) ;In case user has safe-functions = t!
;;;#########################################################################
(defun ses-exercise ()
"Executes all SES coverage tests and displays the results."
(interactive)
(ses-exercise-startup)
;;Run the keyboard-macro tests
(let ((safe-functions nil)
(ses-initial-size '(1 . 1))
(ses-initial-column-width 7)
(ses-initial-default-printer "%.7g")
(ses-after-entry-functions '(forward-char))
(ses-mode-hook nil))
(ses-exercise-macros)
(ses-exercise-signals)
(ses-exercise-invalid-spreadsheets)
;;Upgrade of old-style spreadsheet
(with-temp-buffer
(insert " \n\n \n(ses-cell A1 nil nil nil nil)\n\n(ses-column-widths [7])\n(ses-column-printers [nil])\n(ses-default-printer \"%.7g\")\n\n( ;Global parameters (these are read first)\n 1 ;SES file-format\n 1 ;numrows\n 1 ;numcols\n)\n\n")
(ses-load))
;;ses-vector-delete is always called from buffer-undo-list with the same
;;symbol as argument. We'll give it a different one here.
(let ((x [1 2 3]))
(ses-vector-delete 'x 0 0))
;;ses-create-header-string behaves differently in a non-window environment
;;but we always test under windows.
(let ((window-system (not window-system)))
(scroll-left 7)
(ses-create-header-string))
;;Test for nonstandard after-entry functions
(let ((ses-after-entry-functions '(forward-line))
ses-mode-hook)
(ses-read-cell 0 0 1)
(ses-read-symbol 0 0 t)))
;;Tests with unsafep disabled
(let ((safe-functions t)
ses-mode-hook)
(message "<Checking safe-functions = t>")
(kill-buffer "ses-example.ses")
(find-file "ses-example.ses"))
;;Checks for nonstandard default values for new spreadsheets
(let (ses-mode-hook)
(dolist (x '(("%.6g" 8 (2 . 2))
("%.8g" 6 (3 . 3))))
(let ((ses-initial-size (nth 2 x))
(ses-initial-column-width (nth 1 x))
(ses-initial-default-printer (nth 0 x)))
(with-temp-buffer
(set-buffer-modified-p t)
(ses-mode)))))
;;Test error-handling in command hook, outside a macro.
;;This will ring the bell.
(let (curcell-overlay)
(ses-command-hook))
;;Due to use of run-with-timer, ses-command-hook sometimes gets called
;;after we switch to another buffer.
(switch-to-buffer "*scratch*")
(ses-command-hook)
;;Print results
(message "<Marking source code>")
(testcover-mark-all "ses.el")
(testcover-next-mark)
;;Cleanup
(delete-other-windows)
(kill-buffer "ses-test.txt")
;;Could do this here: (testcover-end "ses.el")
(message "Done"))
;; testcover-ses.el ends here.

View file

@ -0,0 +1,139 @@
;;;; testcover-unsafep.el -- Use testcover to test unsafep's code coverage
;; Copyright (C) 2002 Free Software Foundation, Inc.
;; Author: Jonathan Yavner <jyavner@engineer.com>
;; Maintainer: Jonathan Yavner <jyavner@engineer.com>
;; Keywords: safety lisp utility
;; This file is part of GNU Emacs.
;; GNU Emacs 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.
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
(require 'testcover)
;;;These forms are all considered safe
(defconst testcover-unsafep-safe
'(((lambda (x) (* x 2)) 14)
(apply 'cdr (mapcar '(lambda (x) (car x)) y))
(cond ((= x 4) 5) (t 27))
(condition-case x (car y) (error (car x)))
(dolist (x y) (message "here: %s" x))
(dotimes (x 14 (* x 2)) (message "here: %d" x))
(let (x) (dolist (y '(1 2 3) (1+ y)) (push y x)))
(let (x) (apply '(lambda (x) (* x 2)) 14))
(let ((x '(2))) (push 1 x) (pop x) (add-to-list 'x 2))
(let ((x 1) (y 2)) (setq x (+ x y)))
(let ((x 1)) (let ((y (+ x 3))) (* x y)))
(let* nil (current-time))
(let* ((x 1) (y (+ x 3))) (* x y))
(mapcar (lambda (x &optional y &rest z) (setq y (+ x 2)) (* y 3)) '(1 2 3))
(mapconcat #'(lambda (var) (propertize var 'face 'bold)) '("1" "2") ", ")
(setq buffer-display-count 14 mark-active t)
;;This is not safe if you insert it into a buffer!
(propertize "x" 'display '(height (progn (delete-file "x") 1))))
"List of forms that `unsafep' should decide are safe.")
;;;These forms are considered unsafe
(defconst testcover-unsafep-unsafe
'(( (add-to-list x y)
. (unquoted x))
( (add-to-list y x)
. (unquoted y))
( (add-to-list 'y x)
. (global-variable y))
( (not (delete-file "unsafep.el"))
. (function delete-file))
( (cond (t (aset local-abbrev-table 0 0)))
. (function aset))
( (cond (t (setq unsafep-vars "")))
. (risky-local-variable unsafep-vars))
( (condition-case format-alist 1)
. (risky-local-variable format-alist))
( (condition-case x 1 (error (setq format-alist "")))
. (risky-local-variable format-alist))
( (dolist (x (sort globalvar 'car)) (princ x))
. (function sort))
( (dotimes (x 14) (delete-file "x"))
. (function delete-file))
( (let ((post-command-hook "/tmp/")) 1)
. (risky-local-variable post-command-hook))
( (let ((x (delete-file "x"))) 2)
. (function delete-file))
( (let (x) (add-to-list 'x (delete-file "x")))
. (function delete-file))
( (let (x) (condition-case y (setq x 1 z 2)))
. (global-variable z))
( (let (x) (condition-case z 1 (error (delete-file "x"))))
. (function delete-file))
( (let (x) (mapc (lambda (x) (setcar x 1)) '((1 . 2) (3 . 4))))
. (function setcar))
( (let (y) (push (delete-file "x") y))
. (function delete-file))
( (let* ((x 1)) (setq y 14))
. (global-variable y))
( (mapc 'car (list '(1 . 2) (cons 3 4) (kill-buffer "unsafep.el")))
. (function kill-buffer))
( (mapcar x y)
. (unquoted x))
( (mapcar '(lambda (x) (rename-file x "x")) '("unsafep.el"))
. (function rename-file))
( (mapconcat x1 x2 " ")
. (unquoted x1))
( (pop format-alist)
. (risky-local-variable format-alist))
( (push 1 format-alist)
. (risky-local-variable format-alist))
( (setq buffer-display-count (delete-file "x"))
. (function delete-file))
;;These are actualy safe (they signal errors)
( (apply '(x) '(1 2 3))
. (function (x)))
( (let (((x))) 1)
. (variable (x)))
( (let (1) 2)
. (variable 1))
)
"A-list of (FORM . REASON)... that`unsafep' should decide are unsafe.")
;;;#########################################################################
(defun testcover-unsafep ()
"Executes all unsafep tests and displays the coverage results."
(interactive)
(testcover-unmark-all "unsafep.el")
(testcover-start "unsafep.el")
(let (save-functions)
(dolist (x testcover-unsafep-safe)
(if (unsafep x)
(error "%S should be safe" x)))
(dolist (x testcover-unsafep-unsafe)
(if (not (equal (unsafep (car x)) (cdr x)))
(error "%S should be unsafe: %s" (car x) (cdr x))))
(setq safe-functions t)
(if (or (unsafep '(delete-file "x"))
(unsafep-function 'delete-file))
(error "safe-functions=t should allow delete-file"))
(setq safe-functions '(setcar))
(if (unsafep '(setcar x 1))
(error "safe-functions=(setcar) should allow setcar"))
(if (not (unsafep '(setcdr x 1)))
(error "safe-functions=(setcar) should not allow setcdr")))
(testcover-mark-all "unsafep.el")
(testcover-end "unsafep.el")
(message "Done"))
;; testcover-unsafep.el ends here.

View file

@ -0,0 +1,448 @@
;;;; testcover.el -- Visual code-coverage tool
;; Copyright (C) 2002 Free Software Foundation, Inc.
;; Author: Jonathan Yavner <jyavner@engineer.com>
;; Maintainer: Jonathan Yavner <jyavner@engineer.com>
;; Keywords: lisp utility
;; This file is part of GNU Emacs.
;; GNU Emacs 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.
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; * Use `testcover-start' to instrument a Lisp file for coverage testing.
;; * Use `testcover-mark-all' to add overlay "splotches" to the Lisp file's
;; buffer to show where coverage is lacking. Normally, a red splotch
;; indicates the form was never evaluated; a brown splotch means it always
;; evaluted to the same value.
;; * Use `testcover-next-mark' (bind it to a key!) to jump to the next spot
;; that has a splotch.
;; * Basic algorithm: use `edebug' to mark up the function text with
;; instrumentation callbacks, then replace edebug's callbacks with ours.
;; * To show good coverage, we want to see two values for every form, except
;; functions that always return the same value and `defconst' variables
;; need show only value for good coverage. To avoid the brown splotch, the
;; definitions for constants and 1-valued functions must precede the
;; references.
;; * Use the macro `1value' in your Lisp code to mark spots where the local
;; code environment causes a function or variable to always have the same
;; value, but the function or variable is not intrinsically 1-valued.
;; * Use the macro `noreturn' in your Lisp code to mark function calls that
;; never return, because of the local code environment, even though the
;; function being called is capable of returning in other cases.
;; Problems:
;; * To detect different values, we store the form's result in a vector and
;; compare the next result using `equal'. We don't copy the form's
;; result, so if caller alters it (`setcar', etc.) we'll think the next
;; call has the same value! Also, equal thinks two strings are the same
;; if they differ only in properties.
;; * Because we have only a "1value" class and no "always nil" class, we have
;; to treat as 1-valued any `and' whose last term is 1-valued, in case the
;; last term is always nil. Example:
;; (and (< (point) 1000) (forward-char 10))
;; This form always returns nil. Similarly, `if' and `cond' are
;; treated as 1-valued if all clauses are, in case those values are
;; always nil.
(require 'edebug)
(provide 'testcover)
;;;==========================================================================
;;; User options
;;;==========================================================================
(defgroup testcover nil
"Code-coverage tester"
:group 'lisp
:prefix "testcover-"
:version "21.1")
(defcustom testcover-constants
'(nil t emacs-build-time emacs-version emacs-major-version
emacs-minor-version)
"Variables whose values never change. No brown splotch is shown for
these. This list is quite incomplete!"
:group 'testcover
:type '(repeat variable))
(defcustom testcover-1value-functions
'(backward-char barf-if-buffer-read-only beginning-of-line
buffer-disable-undo buffer-enable-undo current-global-map deactivate-mark
delete-char delete-region ding error forward-char insert insert-and-inherit
kill-all-local-variables lambda mapc narrow-to-region noreturn push-mark
put-text-property run-hooks set-text-properties signal
substitute-key-definition suppress-keymap throw undo use-local-map while
widen yank)
"Functions that always return the same value. No brown splotch is shown
for these. This list is quite incomplete! Notes: Nobody ever changes the
current global map. The macro `lambda' is self-evaluating, hence always
returns the same value (the function it defines may return varying values
when called)."
:group 'testcover
:type 'hook)
(defcustom testcover-noreturn-functions
'(error noreturn throw signal)
"Subset of `testcover-1value-functions' -- these never return. We mark
them as having returned nil just before calling them."
:group 'testcover
:type 'hook)
(defcustom testcover-compose-functions
'(+ - * / length list make-keymap make-sparse-keymap message propertize
replace-regexp-in-string run-with-idle-timer
set-buffer-modified-p)
"Functions that are 1-valued if all their args are either constants or
calls to one of the `testcover-1value-functions', so if that's true then no
brown splotch is shown for these. This list is quite incomplete! Most
side-effect-free functions should be here."
:group 'testcover
:type 'hook)
(defcustom testcover-progn-functions
'(define-key fset function goto-char or overlay-put progn save-current-buffer
save-excursion save-match-data save-restriction save-selected-window
save-window-excursion set set-default setq setq-default
with-output-to-temp-buffer with-syntax-table with-temp-buffer
with-temp-file with-temp-message with-timeout)
"Functions whose return value is the same as their last argument. No
brown splotch is shown for these if the last argument is a constant or a
call to one of the `testcover-1value-functions'. This list is probably
incomplete! Note: `or' is here in case the last argument is a function that
always returns nil."
:group 'testcover
:type 'hook)
(defcustom testcover-prog1-functions
'(prog1 unwind-protect)
"Functions whose return value is the same as their first argument. No
brown splotch is shown for these if the first argument is a constant or a
call to one of the `testcover-1value-functions'."
:group 'testcover
:type 'hook)
(defface testcover-nohits-face
'((t (:background "DeepPink2")))
"Face for forms that had no hits during coverage test"
:group 'testcover)
(defface testcover-1value-face
'((t (:background "Wheat2")))
"Face for forms that always produced the same value during coverage test"
:group 'testcover)
;;;=========================================================================
;;; Other variables
;;;=========================================================================
(defvar testcover-module-constants nil
"Symbols declared with defconst in the last file processed by
`testcover-start'.")
(defvar testcover-module-1value-functions nil
"Symbols declared with defun in the last file processed by
`testcover-start', whose functions always return the same value.")
(defvar testcover-vector nil
"Locally bound to coverage vector for function in progress.")
;;;=========================================================================
;;; Add instrumentation to your module
;;;=========================================================================
;;;###autoload
(defun testcover-start (filename &optional byte-compile)
"Uses edebug to instrument all macros and functions in FILENAME, then
changes the instrumentation from edebug to testcover--much faster, no
problems with type-ahead or post-command-hook, etc. If BYTE-COMPILE is
non-nil, byte-compiles each function after instrumenting."
(interactive "f")
(let ((buf (find-file filename))
(load-read-function 'testcover-read)
(edebug-all-defs t))
(setq edebug-form-data nil
testcover-module-constants nil
testcover-module-1value-functions nil)
(eval-buffer buf))
(when byte-compile
(dolist (x (reverse edebug-form-data))
(when (fboundp (car x))
(message "Compiling %s..." (car x))
(byte-compile (car x))))))
;;;###autoload
(defun testcover-this-defun ()
"Start coverage on function under point."
(interactive)
(let* ((edebug-all-defs t)
(x (symbol-function (eval-defun nil))))
(testcover-reinstrument x)
x))
(defun testcover-read (&optional stream)
"Read a form using edebug, changing edebug callbacks to testcover callbacks."
(let ((x (edebug-read stream)))
(testcover-reinstrument x)
x))
(defun testcover-reinstrument (form)
"Reinstruments FORM to use testcover instead of edebug. This function
modifies the list that FORM points to. Result is non-nil if FORM will
always return the same value."
(let ((fun (car-safe form)))
(cond
((not fun) ;Atom
(or (not (symbolp form))
(memq form testcover-constants)
(memq form testcover-module-constants)))
((consp fun) ;Embedded list
(testcover-reinstrument fun)
(testcover-reinstrument-list (cdr form))
nil)
((or (memq fun testcover-1value-functions)
(memq fun testcover-module-1value-functions))
;;Always return same value
(testcover-reinstrument-list (cdr form))
t)
((memq fun testcover-progn-functions)
;;1-valued if last argument is
(testcover-reinstrument-list (cdr form)))
((memq fun testcover-prog1-functions)
;;1-valued if first argument is
(testcover-reinstrument-list (cddr form))
(testcover-reinstrument (cadr form)))
((memq fun testcover-compose-functions)
;;1-valued if all arguments are
(setq fun t)
(mapc #'(lambda (x) (setq fun (or (testcover-reinstrument x) fun)))
(cdr form))
fun)
((eq fun 'edebug-enter)
;;(edebug-enter 'SYM ARGS #'(lambda nil FORMS))
;; => (testcover-enter 'SYM #'(lambda nil FORMS))
(setcar form 'testcover-enter)
(setcdr (nthcdr 1 form) (nthcdr 3 form))
(let ((testcover-vector (get (cadr (cadr form)) 'edebug-coverage)))
(testcover-reinstrument-list (nthcdr 2 (cadr (nth 2 form))))))
((eq fun 'edebug-after)
;;(edebug-after (edebug-before XXX) YYY FORM)
;; => (testcover-after YYY FORM), mark XXX as ok-coverage
(unless (eq (cadr form) 0)
(aset testcover-vector (cadr (cadr form)) 'ok-coverage))
(setq fun (nth 2 form))
(setcdr form (nthcdr 2 form))
(if (not (memq (car-safe (nth 2 form)) testcover-noreturn-functions))
(setcar form 'testcover-after)
;;This function won't return, so set the value in advance
;;(edebug-after (edebug-before XXX) YYY FORM)
;; => (progn (edebug-after YYY nil) FORM)
(setcar form 'progn)
(setcar (cdr form) `(testcover-after ,fun nil)))
(when (testcover-reinstrument (nth 2 form))
(aset testcover-vector fun '1value)))
((eq fun 'defun)
(if (testcover-reinstrument-list (nthcdr 3 form))
(push (cadr form) testcover-module-1value-functions)))
((eq fun 'defconst)
;;Define this symbol as 1-valued
(push (cadr form) testcover-module-constants)
(testcover-reinstrument-list (cddr form)))
((memq fun '(dotimes dolist))
;;Always returns third value from SPEC
(testcover-reinstrument-list (cddr form))
(setq fun (testcover-reinstrument-list (cadr form)))
(if (nth 2 (cadr form))
fun
;;No third value, always returns nil
t))
((memq fun '(let let*))
;;Special parsing for second argument
(mapc 'testcover-reinstrument-list (cadr form))
(testcover-reinstrument-list (cddr form)))
((eq fun 'if)
;;1-valued if both THEN and ELSE clauses are
(testcover-reinstrument (cadr form))
(let ((then (testcover-reinstrument (nth 2 form)))
(else (testcover-reinstrument-list (nthcdr 3 form))))
(and then else)))
((memq fun '(when unless and))
;;1-valued if last clause of BODY is
(testcover-reinstrument-list (cdr form)))
((eq fun 'cond)
;;1-valued if all clauses are
(testcover-reinstrument-clauses (cdr form)))
((eq fun 'condition-case)
;;1-valued if BODYFORM is and all HANDLERS are
(let ((body (testcover-reinstrument (nth 2 form)))
(errs (testcover-reinstrument-clauses (mapcar #'cdr
(nthcdr 3 form)))))
(and body errs)))
((eq fun 'quote)
;;Don't reinstrument what's inside!
;;This doesn't apply within a backquote
t)
((eq fun '\`)
;;Quotes are not special within backquotes
(let ((testcover-1value-functions
(cons 'quote testcover-1value-functions)))
(testcover-reinstrument (cadr form))))
((eq fun '\,)
;;In commas inside backquotes, quotes are special again
(let ((testcover-1value-functions
(remq 'quote testcover-1value-functions)))
(testcover-reinstrument (cadr form))))
((memq fun '(1value noreturn))
;;Hack - pretend the arg is 1-valued here
(if (symbolp (cadr form)) ;A pseudoconstant variable
t
(let ((testcover-1value-functions
(cons (car (cadr form)) testcover-1value-functions)))
(testcover-reinstrument (cadr form)))))
(t ;Some other function or weird thing
(testcover-reinstrument-list (cdr form))
nil))))
(defun testcover-reinstrument-list (list)
"Reinstruments each form in LIST to use testcover instead of edebug.
This function modifies the forms in LIST. Result is `testcover-reinstrument's
value for the last form in LIST. If the LIST is empty, its evaluation will
always be nil, so we return t for 1-valued."
(let ((result t))
(while (consp list)
(setq result (testcover-reinstrument (pop list))))
result))
(defun testcover-reinstrument-clauses (clauselist)
"Reinstruments each list in CLAUSELIST. Result is t if every
clause is 1-valued."
(let ((result t))
(mapc #'(lambda (x)
(setq result (and (testcover-reinstrument-list x) result)))
clauselist)
result))
(defun testcover-end (buffer)
"Turn off instrumentation of all macros and functions in FILENAME."
(interactive "b")
(let ((buf (find-file-noselect buffer)))
(eval-buffer buf t)))
(defmacro 1value (form)
"For code-coverage testing, indicate that FORM is expected to always have
the same value."
form)
(defmacro noreturn (form)
"For code-coverage testing, indicate that FORM will always signal an error."
form)
;;;=========================================================================
;;; Accumulate coverage data
;;;=========================================================================
(defun testcover-enter (testcover-sym testcover-fun)
"Internal function for coverage testing. Invokes TESTCOVER-FUN while
binding `testcover-vector' to the code-coverage vector for TESTCOVER-SYM
\(the name of the current function)."
(let ((testcover-vector (get testcover-sym 'edebug-coverage)))
(funcall testcover-fun)))
(defun testcover-after (idx val)
"Internal function for coverage testing. Returns VAL after installing it in
`testcover-vector' at offset IDX."
(cond
((eq (aref testcover-vector idx) 'unknown)
(aset testcover-vector idx val))
((not (equal (aref testcover-vector idx) val))
(aset testcover-vector idx 'ok-coverage)))
val)
;;;=========================================================================
;;; Display the coverage data as color splotches on your code.
;;;=========================================================================
(defun testcover-mark (def)
"Marks one DEF (a function or macro symbol) to highlight its contained forms
that did not get completely tested during coverage tests.
A marking of testcover-nohits-face (default = red) indicates that the
form was never evaluated. A marking of testcover-1value-face
\(default = tan) indicates that the form always evaluated to the same value.
The forms throw, error, and signal are not marked. They do not return and
would always get a red mark. Some forms that always return the same
value (e.g., setq of a constant), always get a tan mark that can't be
eliminated by adding more test cases."
(let* ((data (get def 'edebug))
(def-mark (car data))
(points (nth 2 data))
(len (length points))
(changed (buffer-modified-p))
(coverage (get def 'edebug-coverage))
ov j item)
(or (and def-mark points coverage)
(error "Missing edebug data for function %s" def))
(set-buffer (marker-buffer def-mark))
(mapc 'delete-overlay (overlays-in def-mark
(+ def-mark (aref points (1- len)) 1)))
(while (> len 0)
(setq len (1- len)
data (aref coverage len))
(when (and (not (eq data 'ok-coverage))
(setq j (+ def-mark (aref points len))))
(setq ov (make-overlay (1- j) j))
(overlay-put ov 'face
(if (memq data '(unknown 1value))
'testcover-nohits-face
'testcover-1value-face))))
(set-buffer-modified-p changed)))
(defun testcover-mark-all (&optional buffer)
"Mark all forms in BUFFER that did not get completley tested during
coverage tests. This function creates many overlays. SKIPFUNCS is a list
of function-symbols that should not be marked."
(interactive "b")
(if buffer
(switch-to-buffer buffer))
(goto-char 1)
(dolist (x edebug-form-data)
(if (fboundp (car x))
(testcover-mark (car x)))))
(defun testcover-unmark-all (buffer)
"Remove all overlays from FILENAME."
(interactive "b")
(condition-case nil
(progn
(set-buffer buffer)
(mapc 'delete-overlay (overlays-in 1 (buffer-size))))
(error nil))) ;Ignore "No such buffer" errors
(defun testcover-next-mark ()
"Moves point to next line in current buffer that has a splotch."
(interactive)
(goto-char (next-overlay-change (point)))
(end-of-line))
;; testcover.el ends here.

260
lisp/emacs-lisp/unsafep.el Normal file
View file

@ -0,0 +1,260 @@
;;;; unsafep.el -- Determine whether a Lisp form is safe to evaluate
;; Copyright (C) Free Software Foundation, Inc.
;; Author: Jonathan Yavner <jyavner@engineer.com>
;; Maintainer: Jonathan Yavner <jyavner@engineer.com>
;; Keywords: safety lisp utility
;; This file is part of GNU Emacs.
;; GNU Emacs 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.
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; This is a simplistic implementation that does not allow any modification of
;; buffers or global variables. It does no dataflow analysis, so functions
;; like `funcall' and `setcar' are completely disallowed. It is designed
;; for "pure Lisp" formulas, like those in spreadsheets, that don't make any
;; use of the text editing capabilities of Emacs.
;; A formula is safe if:
;; 1. It's an atom.
;; 2. It's a function call to a safe function and all arguments are safe
;; formulas.
;; 3. It's a special form whose arguments are like a function's (and,
;; catch, if, or, prog1, prog2, progn, while, unwind-protect).
;; 4. It's a special form or macro that creates safe temporary bindings
;; (condition-case, dolist, dotimes, lambda, let, let*).
;; 4. It's one of (cond, quote) that have special parsing.
;; 5. It's one of (add-to-list, setq, push, pop) and the assignment variable
;; is safe.
;; 6. It's one of (apply, mapc, mapcar, mapconcat) and its first arg is a
;; quoted safe function.
;;
;; A function is safe if:
;; 1. It's a lambda containing safe formulas.
;; 2. It's a member of list `safe-functions', so the user says it's safe.
;; 3. It's a symbol with the `side-effect-free' property, defined by the
;; byte compiler or function author.
;; 4. It's a symbol with the `safe-function' property, defined here or by
;; the function author. Value t indicates a function that is safe but
;; has innocuous side effects. Other values will someday indicate
;; functions with side effects that are not always safe.
;; The `side-effect-free' and `safe-function' properties are provided for
;; built-in functions and for functions and macros defined in subr.el.
;;
;; A temporary binding is unsafe if its symbol:
;; 1. Has the `risky-local-variable' property.
;; 2. Has a name that ends with -command, font-lock-keywords(-[0-9]+)?,
;; font-lock-syntactic-keywords, -form, -forms, -frame-alist, -function,
;; -functions, -history, -hook, -hooks, -map, -map-alist, -mode-alist,
;; -predicate, or -program.
;;
;; An assignment variable is unsafe if:
;; 1. It would be unsafe as a temporary binding.
;; 2. It doesn't already have a temporary or buffer-local binding.
;; There are unsafe forms that `unsafep' cannot detect. Beware of these:
;; 1. The form's result is a string with a display property containing a
;; form to be evaluated later, and you insert this result into a
;; buffer. Always remove display properties before inserting!
;; 2. The form alters a risky variable that was recently added to Emacs and
;; is not yet marked with the `risky-local-variable' property.
;; 3. The form uses undocumented features of built-in functions that have
;; the `side-effect-free' property. For example, in Emacs-20 if you
;; passed a circular list to `assoc', Emacs would crash. Historically,
;; problems of this kind have been few and short-lived.
(provide 'unsafep)
(require 'byte-opt) ;Set up the `side-effect-free' properties
(defcustom safe-functions nil
"t to disable all safety checks, or a list of assumed-safe functions."
:group 'lisp
:type '(choice (const :tag "No" nil) (const :tag "Yes" t) hook))
(defvar unsafep-vars nil
"Dynamically-bound list of variables that have lexical bindings at this
point in the parse.")
(put 'unsafep-vars 'risky-local-variable t)
;;Side-effect-free functions from subr.el
(dolist (x '(assoc-default assoc-ignore-case butlast last match-string
match-string-no-properties member-ignore-case remove remq))
(put x 'side-effect-free t))
;;Other safe functions
(dolist (x '(;;Special forms
and catch if or prog1 prog2 progn while unwind-protect
;;Safe subrs that have some side-effects
ding error message minibuffer-message random read-minibuffer
signal sleep-for string-match throw y-or-n-p yes-or-no-p
;;Defsubst functions from subr.el
caar cadr cdar cddr
;;Macros from subr.el
save-match-data unless when with-temp-message
;;Functions from subr.el that have side effects
read-passwd split-string replace-regexp-in-string
play-sound-file))
(put x 'safe-function t))
;;;###autoload
(defun unsafep (form &optional unsafep-vars)
"Return nil if evaluating FORM couldn't possibly do any harm; otherwise
result is a reason why FORM is unsafe. UNSAFEP-VARS is a list of symbols
with local bindings."
(catch 'unsafep
(if (or (eq safe-functions t) ;User turned off safety-checking
(atom form)) ;Atoms are never unsafe
(throw 'unsafep nil))
(let* ((fun (car form))
(reason (unsafep-function fun))
arg)
(cond
((not reason)
;;It's a normal function - unsafe if any arg is
(unsafep-progn (cdr form)))
((eq fun 'quote)
;;Never unsafe
nil)
((memq fun '(apply mapc mapcar mapconcat))
;;Unsafe if 1st arg isn't a quoted lambda
(setq arg (cadr form))
(cond
((memq (car-safe arg) '(quote function))
(setq reason (unsafep-function (cadr arg))))
((eq (car-safe arg) 'lambda)
;;Self-quoting lambda
(setq reason (unsafep arg unsafep-vars)))
(t
(setq reason `(unquoted ,arg))))
(or reason (unsafep-progn (cddr form))))
((eq fun 'lambda)
;;First arg is temporary bindings
(mapc #'(lambda (x)
(let ((y (unsafep-variable x t)))
(if y (throw 'unsafep y)))
(or (memq x '(&optional &rest))
(push x unsafep-vars)))
(cadr form))
(unsafep-progn (cddr form)))
((eq fun 'let)
;;Creates temporary bindings in one step
(setq unsafep-vars (nconc (mapcar #'unsafep-let (cadr form))
unsafep-vars))
(unsafep-progn (cddr form)))
((eq fun 'let*)
;;Creates temporary bindings iteratively
(dolist (x (cadr form))
(push (unsafep-let x) unsafep-vars))
(unsafep-progn (cddr form)))
((eq fun 'setq)
;;Safe if odd arguments are local-var syms, evens are safe exprs
(setq arg (cdr form))
(while arg
(setq reason (or (unsafep-variable (car arg) nil)
(unsafep (cadr arg) unsafep-vars)))
(if reason (throw 'unsafep reason))
(setq arg (cddr arg))))
((eq fun 'pop)
;;safe if arg is local-var sym
(unsafep-variable (cadr form) nil))
((eq fun 'push)
;;Safe if 2nd arg is a local-var sym
(or (unsafep (cadr form) unsafep-vars)
(unsafep-variable (nth 2 form) nil)))
((eq fun 'add-to-list)
;;Safe if first arg is a quoted local-var sym
(setq arg (cadr form))
(if (not (eq (car-safe arg) 'quote))
`(unquoted ,arg)
(or (unsafep-variable (cadr arg) nil)
(unsafep-progn (cddr form)))))
((eq fun 'cond)
;;Special form with unusual syntax - safe if all args are
(dolist (x (cdr form))
(setq reason (unsafep-progn x))
(if reason (throw 'unsafep reason))))
((memq fun '(dolist dotimes))
;;Safe if COUNT and RESULT are safe. VAR is bound while checking BODY.
(setq arg (cadr form))
(or (unsafep-progn (cdr arg))
(let ((unsafep-vars (cons (car arg) unsafep-vars)))
(unsafep-progn (cddr form)))))
((eq fun 'condition-case)
;;Special form with unusual syntax - safe if all args are
(or (unsafep-variable (cadr form) t)
(unsafep (nth 2 form) unsafep-vars)
(let ((unsafep-vars (cons (cadr form) unsafep-vars)))
;;var is bound only during handlers
(dolist (x (nthcdr 3 form))
(setq reason (unsafep-progn (cdr x)))
(if reason (throw 'unsafep reason))))))
(t
;;First unsafep-function call above wasn't nil, no special case applies
reason)))))
(defun unsafep-function (fun)
"Return nil if FUN is a safe function (either a safe lambda or a
symbol that names a safe function). Otherwise result is a reason code."
(cond
((eq (car-safe fun) 'lambda)
(unsafep fun unsafep-vars))
((not (and (symbolp fun)
(or (get fun 'side-effect-free)
(eq (get fun 'safe-function) t)
(eq safe-functions t)
(memq fun safe-functions))))
`(function ,fun))))
(defun unsafep-progn (list)
"Return nil if all forms in LIST are safe, or the reason for the first
unsafe form."
(catch 'unsafep-progn
(let (reason)
(dolist (x list)
(setq reason (unsafep x unsafep-vars))
(if reason (throw 'unsafep-progn reason))))))
(defun unsafep-let (clause)
"CLAUSE is a let-binding, either SYM or (SYM) or (SYM VAL). Throws a
reason to `unsafep' if VAL isn't safe. Returns SYM."
(let (reason sym)
(if (atom clause)
(setq sym clause)
(setq sym (car clause)
reason (unsafep (cadr clause) unsafep-vars)))
(setq reason (or (unsafep-variable sym t) reason))
(if reason (throw 'unsafep reason))
sym))
(defun unsafep-variable (sym global-okay)
"Returns nil if SYM is lexically bound or is a non-risky buffer-local
variable, otherwise a reason why it is unsafe. Failing to be locally bound
is okay if GLOBAL-OKAY is non-nil."
(cond
((not (symbolp sym))
`(variable ,sym))
((risky-local-variable-p sym)
`(risky-local-variable ,sym))
((not (or global-okay
(memq sym unsafep-vars)
(local-variable-p sym)))
`(global-variable ,sym))))
;; unsafep.el ends here.

View file

@ -1617,6 +1617,7 @@ in that case, this function acts as if `enable-local-variables' were t."
;; and after the .scm.[0-9] and CVS' <file>.<rev> patterns too.
("\\.[1-9]\\'" . nroff-mode)
("\\.g\\'" . antlr-mode)
("\\.ses\\'" . ses-mode)
("\\.in\\'" nil t)))
"Alist of filename patterns vs corresponding major mode functions.
Each element looks like (REGEXP . FUNCTION) or (REGEXP FUNCTION NON-NIL).
@ -2010,6 +2011,7 @@ is specified, returning t if it is specified."
(put 'ignored-local-variables 'risky-local-variable t)
(put 'eval 'risky-local-variable t)
(put 'file-name-handler-alist 'risky-local-variable t)
(put 'inhibit-quit 'risky-local-variable t)
(put 'minor-mode-alist 'risky-local-variable t)
(put 'minor-mode-map-alist 'risky-local-variable t)
(put 'minor-mode-overriding-map-alist 'risky-local-variable t)
@ -2058,6 +2060,14 @@ is specified, returning t if it is specified."
;; This one is safe because the user gets to check it before it is used.
(put 'compile-command 'safe-local-variable t)
(defun risky-local-variable-p (sym)
"Returns non-nil if SYM could be dangerous as a file-local variable."
(or (memq sym ignored-local-variables)
(get sym 'risky-local-variable)
(and (string-match "-hooks?$\\|-functions?$\\|-forms?$\\|-program$\\|-command$\\|-predicate$\\|font-lock-keywords$\\|font-lock-keywords-[0-9]+$\\|font-lock-syntactic-keywords$\\|-frame-alist$\\|-mode-alist$\\|-map$\\|-map-alist$"
(symbol-name sym))
(not (get sym 'safe-local-variable)))))
(defcustom safe-local-eval-forms nil
"*Expressions that are considered \"safe\" in an `eval:' local variable.
Add expressions to this list if you want Emacs to evaluate them, when
@ -2122,15 +2132,9 @@ is considered risky."
((eq var 'coding)
;; We have already handled coding: tag in set-auto-coding.
nil)
((memq var ignored-local-variables)
nil)
;; "Setting" eval means either eval it or do nothing.
;; Likewise for setting hook variables.
((or (get var 'risky-local-variable)
(and
(string-match "-hooks?$\\|-functions?$\\|-forms?$\\|-program$\\|-command$\\|-predicate$\\|font-lock-keywords$\\|font-lock-keywords-[0-9]+$\\|font-lock-syntactic-keywords$\\|-frame-alist$\\|-mode-alist$\\|-map$\\|-map-alist$"
(symbol-name var))
(not (get var 'safe-local-variable))))
((risky-local-variable-p var)
;; Permit evalling a put of a harmless property.
;; if the args do nothing tricky.
(if (or (and (eq var 'eval)

2914
lisp/ses.el Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,12 @@
2002-09-16 Jonathan Yavner <jyavner@engineer.com>
* variables.texi (File Local Variables): New function
risky-local-variable-p.
2002-09-15 Jonathan Yavner <jyavner@engineer.com>
* functions.texi (Function safety): New node about unsafep.
2002-08-05 Per Abrahamsen <abraham@dina.kvl.dk>
* customize.texi (Splicing into Lists): Fixed example.

View file

@ -22,6 +22,7 @@ define them.
* Function Cells:: Accessing or setting the function definition
of a symbol.
* Inline Functions:: Defining functions that the compiler will open code.
* Function safety:: Determining whether a function is safe to call.
* Related Topics:: Cross-references to specific Lisp primitives
that have a special bearing on how functions work.
@end menu
@ -1157,6 +1158,95 @@ do for macros. (@xref{Argument Evaluation}.)
Inline functions can be used and open-coded later on in the same file,
following the definition, just like macros.
@node Function safety
@section Determining whether a function is safe to call
@cindex function safety
@cindex safety of functions
@cindex virus detection
@cindex Trojan-horse detection
@cindex DDoS attacks
Some major modes such as SES (see @pxref{Top,,,ses}) will call
functions that are stored in user files. User files sometimes have
poor pedigrees---you can get a spreadsheet from someone you've just
met, or you can get one through email from someone you've never met.
Such files can contain viruses and other Trojan horses that could
corrupt your operating system environment, delete your files, or even
turn your computer into a DDoS zombie! To avoid this terrible fate,
you should not call a function whose source code is stored in a user
file until you have determined that it is safe.
@defun unsafep form &optional unsafep-vars
Returns nil if @var{form} is a @dfn{safe} lisp expression, or returns
a list that describes why it might be unsafe. The argument
@var{unsafep-vars} is a list of symbols known to have temporary
bindings at this point; it is mainly used for internal recursive
calls. The current buffer is an implicit argument, which provides a
list of buffer-local bindings.
@end defun
Being quick and simple, @code{unsafep} does a very light analysis and
rejects many Lisp expressions that are actually safe. There are no
known cases where @code{unsafep} returns nil for an unsafe expression.
However, a ``safe'' Lisp expression can return a string with a
@code{display} property, containing an associated Lisp expression to
be executed after the string is inserted into a buffer. This
associated expression can be a virus. In order to be safe, you must
delete properties from all strings calculated by user code before
inserting them into buffers.
What is a safe Lisp expression? Basically, it's an expression that
calls only built-in functions with no side effects (or only innocuous
ones). Innocuous side effects include displaying messages and
altering non-risky buffer-local variables (but not global variables).
@table @dfn
@item Safe expression
@itemize
@item
An atom or quoted thing.
@item
A call to a safe function (see below), if all its arguments are
safe expressions.
@item
One of the special forms [and, catch, cond, if, or, prog1, prog2,
progn, while, unwind-protect], if all its arguments are safe.
@item
A form that creates temporary bindings [condition-case, dolist,
dotimes, lambda, let, let*], if all args are safe and the symbols to
be bound are not explicitly risky (see @pxref{File Local Variables}).
@item
An assignment [add-to-list, setq, push, pop], if all args are safe and
the symbols to be assigned are not explicitly risky and they already
have temporary or buffer-local bindings.
@item
One of [apply, mapc, mapcar, mapconcat] if the first argument is a
safe explicit lambda and the other args are safe expressions.
@end itemize
@item Safe function
@itemize
@item
A lambda containing safe expressions.
@item
A symbol on the list @code{safe-functions}, so the user says it's safe.
@item
A symbol with a non-nil @code{side-effect-free} property.
@item
A symbol with a non-nil @code{safe-function} property. Value t
indicates a function that is safe but has innocuous side effects.
Other values will someday indicate functions with classes of side
effects that are not always safe.
@end itemize
The @code{side-effect-free} and @code{safe-function} properties are
provided for built-in functions and for low-level functions and macros
defined in @file{subr.el}. You can assign these properties for the
functions you write.
@end table
@c Emacs versions prior to 19 did not have inline functions.
@node Related Topics

View file

@ -1738,15 +1738,20 @@ be called later, or an expression that will be executed later, simply
visiting a file could take over your Emacs. To prevent this, Emacs
takes care not to allow local variable lists to set such variables.
For one thing, any variable whose name ends in @samp{-function},
@samp{-functions}, @samp{-hook}, @samp{-hooks}, @samp{-form},
@samp{-forms}, @samp{-program}, @samp{-command} or @samp{-predicate}
cannot be set in a local variable list. In general, you should use such
a name whenever it is appropriate for the variable's meaning.
For one thing, any variable whose name ends in @samp{-command},
@same{-frame-alist}, @samp{-function}, @samp{-functions},
@samp{-hook}, @samp{-hooks}, @samp{-form}, @samp{-forms}, @samp{-map},
@samp{-map-alist}, @samp{-mode-alist}, @samp{-program}, or
@samp{-predicate} cannot be set in a local variable list. In general,
you should use such a name whenever it is appropriate for the
variable's meaning. The variables @samp{font-lock-keywords},
@samp{font-lock-keywords-[0-9]}, and
@samp{font-lock-syntactic-keywords} cannot be set in a local variable
list, either.
In addition, any variable whose name has a non-@code{nil}
@code{risky-local-variable} property is also ignored. So are
all variables listed in @code{ignored-local-variables}:
@code{risky-local-variable} property is also ignored. So are all
variables listed in @code{ignored-local-variables}:
@defvar ignored-local-variables
This variable holds a list of variables that should not be
@ -1754,6 +1759,10 @@ set by a file's local variables list. Any value specified
for one of these variables is ignored.
@end defvar
@defun risky-local-variable-p sym
Returns non-nil if @var{sym} is risky for any of the reasons stated above.
@end defun
The @samp{Eval:} ``variable'' is also a potential loophole, so Emacs
normally asks for confirmation before handling it.

View file

@ -1,3 +1,9 @@
2002-09-10 Jonathan Yavner <jyavner@engineer.com>
* Makefile.in (INFO_TARGETS, DVI_TARGETS): Add SES.
(../info/ses, ses.dvi): New targets.
* ses.texi: New file.
2002-09-06 Pavel Jan,Bm(Bk <Pavel@Janik.cz>
* texinfo.tex: Updated to texinfo 4.2.

View file

@ -39,13 +39,13 @@ INFO_TARGETS = ../info/emacs ../info/ccmode ../info/cl \
../info/efaq ../info/ada-mode ../info/autotype ../info/calc \
../info/idlwave ../info/eudc ../info/ebrowse ../info/pcl-cvs \
../info/woman ../info/emacs-mime ../info/eshell \
../info/speedbar ../info/tramp
../info/speedbar ../info/tramp ../info/ses
DVI_TARGETS = emacs.dvi calc.dvi cc-mode.dvi cl.dvi dired-x.dvi \
ediff.dvi forms.dvi gnus.dvi message.dvi mh-e.dvi \
reftex.dvi sc.dvi vip.dvi viper.dvi widget.dvi faq.dvi \
ada-mode.dvi autotype.dvi idlwave.dvi eudc.dvi ebrowse.dvi \
pcl-cvs.dvi woman.dvi emacs-mime.dvi eshell.dvi \
speedbar.dvi tramp.dvi
speedbar.dvi tramp.dvi ses.dvi
INFOSOURCES = info.texi
# The following rule does not work with all versions of `make'.
@ -272,6 +272,11 @@ emacs-mime.dvi: emacs-mime.texi
tramp.dvi: tramp.texi
$(ENVADD) $(TEXI2DVI) ${srcdir}/tramp.texi
../info/ses: ses.texi
cd $(srcdir); $(MAKEINFO) ses.texi
ses.dvi: ses.texi
$(ENVADD) $(TEXI2DVI) ${srcdir}/ses.texi
mostlyclean:
rm -f *.log *.cp *.fn *.ky *.pg *.vr core *.tp *.core gnustmp.*

860
man/ses.texi Normal file
View file

@ -0,0 +1,860 @@
\input texinfo @c -*-texinfo-*-
@c %**start of header
@setfilename ../info/ses
@settitle SES: Simple Emacs Spreadsheet
@setchapternewpage off
@c %**end of header
@dircategory Emacs
@direntry
* SES: (ses). Simple Emacs Spreadsheet
@end direntry
@ifinfo
This file documents SES: the Simple Emacs Spreadsheet.
Copyright @copyright{} 2002 Free Software Foundation, Inc.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1 or
any later version published by the Free Software Foundation; with no
Invariant Sections, with the Front-Cover texts being ``A GNU
Manual,'' and with the Back-Cover Texts as in (a) below. A copy of the
license is included in the section entitled ``GNU Free Documentation
License'' in the Emacs manual.
(a) The FSF's Back-Cover Text is: ``You have freedom to copy and modify
this GNU Manual, like GNU software. Copies published by the Free
Software Foundation raise funds for GNU development.''
This document is part of a collection distributed under the GNU Free
Documentation License. If you want to distribute this document
separately from the collection, you can do so by adding a copy of the
license to the document, as described in section 6 of the license.
@end ifinfo
@finalout
@titlepage
@title SES
@subtitle Simple Emacs Spreadsheet
@author Jonathan A. Yavner
@author @email{jyavner@@engineer.com}
@comment The following two commands start the copyright page.
@page
@vskip 0pt plus 1filll
@noindent
Copyright @copyright{} 2002 Free Software Foundation, Inc.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.1 or
any later version published by the Free Software Foundation; with no
Invariant Sections, with the Front-Cover texts being ``A GNU
Manual'', and with the Back-Cover Texts as in (a) below. A copy of the
license is included in the section entitled ``GNU Free Documentation
License'' in the Emacs manual.
(a) The FSF's Back-Cover Text is: ``You have freedom to copy and modify
this GNU Manual, like GNU software. Copies published by the Free
Software Foundation raise funds for GNU development.''
This document is part of a collection distributed under the GNU Free
Documentation License. If you want to distribute this document
separately from the collection, you can do so by adding a copy of the
license to the document, as described in section 6 of the license.
@end titlepage
@contents
@c ===================================================================
@ifnottex
@node Top, Introduction, (dir), (dir)
@comment node-name, next, previous, up
@top SES: Simple Emacs Spreadsheet
@display
SES is a major mode for GNU Emacs to edit spreadsheet files, which
contain a rectangular grid of cells. The cells' values are specified
by formulas that can refer to the values of other cells.
@end display
@end ifnottex
This (such as @pxref{Top,calc,,calc}) is good.
To report bugs, send email to @email{jyavner@@engineer.com}.
@menu
* Sales pitch:: Why use SES?
* The Basics:: Basic spreadsheet commands
* Advanced Features:: Want to know more?
* For Gurus:: Want to know @emph{even more}?
* Acknowledgements:: Acknowledgements
@end menu
@c ===================================================================
@node Sales Pitch, The Basics, Top, Top
@comment node-name, next, previous, up
@chapter Sales Pitch
@itemize @bullet
@item Create and edit simple spreadsheets with a minimum of fuss.
@item Full undo/redo/autosave.
@item Immune to viruses in spreadsheet files.
@item Cell formulas are straight Emacs Lisp.
@item Printer functions for control of cell appearance.
@item Intuitive keystroke commands: C-o = insert row, M-o = insert column, etc.
@item ``Spillover'' of lengthy cell values into following blank cells.
@item Header line shows column letters or a selected row.
@item Completing-read for entering symbols as cell values.
@item Cut, copy, and paste can transfer formulas and printer functions.
@item Import and export of tab-separated values or tab-separated formulas.
@item Plaintext, easily-hacked file format.
@end itemize
@c ===================================================================
@node The Basics, Advanced Features, Sales Pitch, Top
@comment node-name, next, previous, up
@chapter The Basics
A @dfn{cell identifier} is a symbol with a column letter and a row
number. Cell B7 is the 2nd column of the 7th row. For very wide
spreadsheets, there are two column letters: cell AB7 is the 28th
column of the 7th row.
@table @kbd
@item j
Moves point to cell, specified by identifier (@code{ses-jump}).
@end table
Point is always at the left edge of a cell, or at the empty endline.
When mark is inactive, the current cell is underlined. When mark is
active, the range is the highlighted rectangle of cells (SES always
uses transient mark mode). Drag the mouse from A1 to A3 to create the
range A1-A2. Many SES commands operate only on single cells, not
ranges.
@table @kbd
@item C-SPC
@itemx C-@@
Set mark at point (@code{set-mark-command}).
@item C-g
Turn off the mark (@code{keyboard-quit}).
@item M-h
Highlight current row (@code{ses-mark-row}).
@item S-M-h
Highlight current column (@code{ses-mark-column}).
@item C-x h
Highlight all cells (@code{mark-whole-buffer}).
@end table
@menu
* Formulas::
* Resizing::
* Printer functions::
* Clearing cells::
* Copy/cut/paste::
* Customizing SES::
@end menu
@node Formulas, Resizing, The Basics, The Basics
@section Cell formulas
To enter a number into the current cell, just start typing:
@table @kbd
@item 0..9
Self-insert a digit (@code{ses-read-cell}).
@item -
Self-insert a negative number (@code{ses-read-cell}).
@item .
Self-insert a fractional number (@code{ses-read-cell}).
@item "
Self-insert a quoted string. The ending double-quote
is inserted for you (@code{ses-read-cell}).
@item (
Self-insert an expression. The right-parenthesis is inserted for you
(@code{ses-read-cell}). To access another cell's value, just use its
identifier in your expression. Whenever the other cell is changed,
this cell's formula will be reevaluated. While typing in the
expression, you can use @kbd{M-TAB} to complete symbol names.
@item ' @r{(apostrophe)}
Enter a symbol (ses-read-symbol). SES remembers all symbols that have
been used as formulas, so you can type just the beginning of a symbol
and use @kbd{SPC}, @kbd{TAB}, and @kbd{?} to complete it.
@end table
To enter something else (e.g., a vector), begin with a digit, then
erase the digit and type whatever you want.
@table @kbd
@item RET
Edit the existing formula in the current cell (@code{ses-edit-cell}).
@item C-c C-c
Force recalculation of the current cell or range (@code{ses-recalculate-cell}).
@item C-c C-l
Recalculate the entire spreadsheet (@code{ses-recalculate-all}).
@end table
@node Resizing, Printer functions, Formulas, The Basics
@section Resizing the spreadsheet
Basic commands:
@table @kbd
@item C-o
(@code{ses-insert-row})
@item M-o
(@code{ses-insert-column})
@item C-k
(@code{ses-delete-row})
@item M-k
(@code{ses-delete-column})
@item w
(@code{ses-set-column-width})
@item TAB
Moves point to the next rightward cell, or inserts a new column if
already at last cell on line, or inserts a new row if at endline
(@code{ses-forward-or-insert}).
@item C-j
Linefeed inserts below the current row and moves to column A
(@code{ses-append-row-jump-first-column}).
@end table
Resizing the spreadsheet (unless you're just changing a column width)
relocates all the cell-references in formulas so they still refer to
the same cells. If a formula mentioned B1 and you insert a new first
row, the formula will now mention B2.
If you delete a cell that a formula refers to, the cell-symbol is
deleted from the formula, so @code{(+ A1 B1 C1)} after deleting the third
column becomes @code{(+ A1 B1)}. In case this is not what you wanted:
@table @kbd
@item C-_
@itemx C-x u
Undo previous action (@code{(undo)}).
@end table
@node Printer functions, Clearing cells, Resizing, The Basics
@section Printer functions
Printer functions convert binary cell values into the print forms that
Emacs will display on the screen.
A printer can be a format string, like @samp{"$%.2f"}. The result
string is right-aligned within the print cell. To get left-alignment,
use parentheses: @samp{("$%.2f")}. A printer can also be a
one-argument function (a symbol or a lambda), whose result is a string
(right-aligned) or list of one string (left-aligned). While typing in
a lambda, you can use @kbd{M-TAB} to complete the names of symbols.
Each cell has a printer. If nil, the column-printer for the cell's
column is used. If that is also nil, the default-printer for the
spreadsheet is used.
@table @kbd
@item p
Enter a printer for current cell or range (@code{ses-read-cell-printer}).
@item M-p
Enter a printer for the current column (@code{ses-read-column-printer}).
@item C-c C-p
Enter the default printer for the spreadsheet
(@code{ses-read-default-printer}).
@end table
The @code{ses-read-@r{XXX}-printer} commands have their own minibuffer
history, which is preloaded with the set of all printers used in this
spreadsheet, plus the standard printers.
The standard printers are suitable only for cells, not columns or
default, because they format the value using the column-printer (or
default-printer if nil) and then center the result:
@table @code
@item ses-center
Just centering.
@item ses-center-span
Centering with spill-over to following blank cells.
@item ses-dashfill
Centering using dashes (-) instead of spaces.
@item ses-dashfill-span
Centering with dashes and spill-over.
@item ses-tildefill-span
Centering with tildes (~) and spill-over.
@end table
@node Clearing cells, Copy/cut/paste, Printer functions, The Basics
@section Clearing cells
These commands set both formula and printer to nil:
@table @kbd
@item DEL
Clear cell and move left (@code{ses-clear-cell-backward}).
@item C-d
Clear cell and move right (@code{ses-clear-cell-forward}).
@end table
@node Copy/cut/paste, Customizing SES, Clearing cells, The Basics
@section Copy, cut, and paste
The copy functions work on rectangular regions of cells. You can paste the
copies into non-SES buffers to export the print text.
@table @kbd
@item M-w
@itemx [copy]
@itemx [C-insert]
Copy the highlighted cells to kill ring and primary clipboard
(@code{kill-ring-save}).
@item [drag-mouse-1]
Mark a region and copy it to kill ring and primary clipboard
(@code{mouse-set-region}).
@item [M-drag-mouse-1]
Mark a region and copy it to kill ring and secondary clipboard
(@code{mouse-set-secondary}).
@item C-w
@itemx [cut]
@itemx [S-delete]
The cut functions do not actually delete rows or columns - they copy
and then clear (@code{ses-kill-override}).
@item C-y
@itemx [S-insert]
Paste from kill ring (@code{yank}). The paste functions behave
differently depending on the format of the text being inserted:
@itemize @bullet
@item
When pasting cells that were cut from a SES buffer, the print text is
ignored and only the attached formula and printer are inserted; cell
references in the formula are relocated unless you use @kbd{C-u}.
@item
The pasted text overwrites a rectangle of cells whose top left corner
is the current cell. If part of the rectangle is beyond the edges of
the spreadsheet, you must confirm the increase in spreadsheet size.
@item
Non-SES text is usually inserted as a replacement formula for the
current cell. If the formula would be a symbol, it's treated as a
string unless you use @kbd{C-u}. Pasted formulas with syntax errors
are always treated as strings.
@end itemize
@item [paste]
Paste from primary clipboard or kill ring (@code{clipboard-yank}).
@item [mouse-2]
Set point and paste from primary clipboard (@code{mouse-yank-at-click}).
@item [M-mouse-2]
Set point and paste from secondary clipboard (@code{mouse-yank-secondary}).
@item M-y
Immediately after a paste, you can replace the text with a preceding
element from the kill ring (@code{ses-yank-pop}). Unlike the standard
Emacs yank-pop, the SES version uses @code{undo} to delete the old
yank. This doesn't make any difference?
@end table
@node Customizing SES, , Copy/cut/paste, The Basics
@section Customizing SES
By default, a newly-created spreadsheet has 1 row and 1 column. The
column width is 7 and the default printer is @samp{"%.7g"}. Each of these
can be customized. Look in group ``ses''.
After entering a cell value, point normally moves right to the next
cell. You can customize @code{ses-after-entry-functions} to move left or
up or down. For diagonal movement, select two functions from the
list.
@code{ses-mode-hook} is a normal mode hook (list of functions to
execute when starting SES mode for a buffer).
The variable @code{safe-functions} is a a list of possibly-unsafe
functions to be treated as safe when analysing formulas and printers.
@xref{Virus protection}. Before customizing @code{safe-functions},
think about how much you trust the person who's suggesting this
change. The value t turns off all anti-virus protection. A
list-of-functions value might enable a ``gee whiz'' spreadsheet, but it
also creates trapdoors in your anti-virus armor. In order for virus
protection to work, you must always press @kbd{n} when presented with
a virus warning, unless you understand what the questionable code is
trying to do. Do not listen to those who tell you to customize
@code{enable-local-eval}---this variable is for people who don't wear
safety belts!
@c ===================================================================
@node Advanced Features, For Gurus, The Basics, Top
@chapter Advanced Features
@table @kbd
@item C-c M-C-h
(@code{ses-read-header-row}). The header line at the top of the SES
window normally shows the column letter for each column. You can set
it to show a copy of some row, such as a row of column titles, so that
row will always be visible. Set the header line to row 0 to show
column letters again.
@end table
@menu
* The print area::
* Ranges in formulas::
* Sorting by column::
* Standard formula functions::
* More on cell printing::
* Import and export::
* Virus protection::
* Spreadsheets with details and summary::
@end menu
@node The print area, Ranges in formulas, Advanced Features, Advanced Features
@section The print area
A SES file consists of a print area and a data area. Normally the
buffer is narrowed to show only the print area. The print area is
read-only except for special SES commands; it contains cell values
formatted by printer functions. The data area records the formula and
printer functions, etc.
@table @kbd
@item C-x n w
Show print and data areas (@code{widen}).
@item C-c C-n
Show only print area (@code{ses-renarrow-buffer}).
@item S-C-l
@itemx M-C-l
Recreate print area by reevaluating printer functions for all cells
(@code{ses-reprint-all}).
@end table
@node Ranges in formulas, Sorting by column, The print area, Advanced Features
@section Ranges in formulas
A formula like
@lisp
(+ A1 A2 A3)
@end lisp
is the sum of three specific cells. If you insert a new second row,
the formula becomes
@lisp
(+ A1 A3 A4)
@end lisp
and the new row is not included in the sum.
The macro @code{(ses-range @var{from} @var{to})} evalutes to a list of
the values in a rectangle of cells. If your formula is
@lisp
(apply '+ (ses-range A1 A3))
@end lisp
and you insert a new second row, it becomes
@lisp
(apply '+ (ses-range A1 A4))
@end lisp
and the new row is included in the sum.
While entering or editing a formula in the minibuffer, you can select
a range in the spreadsheet (using mouse or keyboard), then paste a
representation of that range into your formula. Suppose you select
A1-C1:
@table @kbd
@item [S-mouse-3]
Inserts "A1 B1 C1" @code{(ses-insert-range-click})
@item C-c C-r
Keyboard version (@code{ses-insert-range}).
@item [C-S-mouse-3]
Inserts "(ses-range A1 C1)" (@code{ses-insert-ses-range-click}).
@item C-c C-s
Keyboard version (@code{ses-insert-ses-range}).
@end table
If you delete the @var{from} or @var{to} cell for a range, the nearest
still-existing cell is used instead. If you delete the entire range,
the formula relocator will delete the ses-range from the formula.
If you insert a new row just beyond the end of a one-column range, or
a new column just beyond a one-row range, the new cell is included in
the range. New cells inserted just before a range are not included.
@node Sorting by column, Standard formula functions, Ranges in formulas, Advanced Features
@section Sorting by column
@table @kbd
@item C-c M-C-s
Sort the cells of a range using one of the columns
(@code{ses-sort-column}). The rows (or partial rows if the range
doesn't include all columns) are rearranged so the chosen column will
be in order.
@item [header-line mouse-2]
The easiest way to sort is to click mouse-2 on the chosen column's header row
(@code{ses-sort-column-click}).
@end table
The sort comparison uses @code{string<}, which works well for
right-justified numbers and left-justified strings.
With prefix arg, sort is in descending order.
Rows are moved one at a time, with relocation of formulas. This works
well if formulas refer to other cells in their row, not so well for
formulas that refer to other rows in the range or to cells outside the
range.
@node Standard formula functions, More on cell printing, Sorting by column, Advanced Features
@section Standard formula functions
Oftentimes you want a calculation to exclude the blank cells. Here
are some useful functions to call from your formulas:
@table @code
@item (ses-delete-blanks &rest @var{args})
Returns a list from which all blank cells (value is either nil or
'*skip*) have been deleted.
@item (ses+ &rest @var{args})
Sum of non-blank arguments.
@item (ses-average @var{list})
Average of non-blank elements in @var{list}. Here the list is passed
as a single argument, since you'll probably use it with @code{ses-range}.
@end table
@node More on cell printing, Import and export, Standard formula functions, Advanced Features
@section More on cell printing
Special cell values:
@itemize
@item nil prints the same as "", but allows previous cell to spill over.
@item '*skip* replaces nil when the previous cell actually does spill over;
nothing is printed for it.
@item '*error* indicates that the formula signalled an error instead of
producing a value: the print cell is filled with hash marks (#).
@end itemize
If the result from the printer function is too wide for the cell and
the following cell is nil, the result will spill over into the
following cell. Very wide results can spill over several cells. If
the result is too wide for the available space (up to the end of the
row or the next non-nil cell), the result is truncated if the cell's
value is a string, or replaced with hash marks otherwise.
SES could get confused by printer results that contain newlines or
tabs, so these are replaced with question marks.
@table @kbd
@item C-c C-t
Confine a cell to its own column (@code{ses-truncate-cell}). This
alows you to move point to a rightward cell that would otherwise be
covered by a spill-over. If you don't change the rightward cell, the
confined cell will spill over again the next time it is reprinted.
@item C-c C-c
When applied to a single cell, this command displays in the echo area any
formula error or printer error that occurred during
recalculation/reprinting (@code{ses-recalculate-cell}).
@end table
When a printer function signals an error, the default printer
@samp{"%s"} is substituted. This is useful when your column printer
is numeric-only and you use a string as a cell value.
@node Import and export, Virus protection, More on cell printing, Advanced Features
@section Import and export
@table @kbd
@item x t
Export a range of cells as tab-separated values (@code{ses-export-tsv}).
@item x T
Export a range of cells as tab-separated formulas (@code{ses-export-tsf}).
@end table
The exported text goes to the kill ring --- you can paste it into
another buffer. Columns are separated by tabs, rows by newlines.
To import text, use any of the yank commands where the text to paste
contains tabs and/or newlines. Imported formulas are not relocated.
@node Virus protection, Spreadsheets with details and summary, Import and export, Advanced Features
@section Virus protection
Whenever a formula or printer is read from a file or is pasted into
the spreadsheet, it receives a ``needs safety check'' marking. Later,
when the formula or printer is evaluated for the first time, it is
checked for safety using the @code{unsafep} predicate; if found to be
``possibly unsafe'', the questionable formula or printer is displayed
and you must press Y to approve it or N to use a substitute. The
substitute always signals an error.
Formulas or printers that you type in are checked immediately for
safety. If found to be possibly unsafe and you press N to disapprove,
the action is cancelled and the old formula or printer will remain.
Besides viruses (which try to copy themselves to other files),
@code{unsafep} can also detect all other kinds of Trojan horses, such as
spreadsheets that delete files, send email, flood Web sites, alter
your Emacs settings, etc.
Generally, spreadsheet formulas and printers are simple things that
don't need to do any fancy computing, so all potentially-dangerous
parts of the Emacs Lisp environment can be excluded without cramping
your style as a formula-writer. See the documentation in @file{unsafep.el}
for more info on how Lisp forms are classified as safe or unsafe.
@node Spreadsheets with details and summary, , Virus protection, Advanced Features
@section Spreadsheets with details and summary
A common organization for spreadsheets is to have a bunch of ``detail''
rows, each perhaps describing a transaction, and then a set of
``summary'' rows that each show reduced data for some subset of the
details. SES supports this organization via the @code{ses-select}
function.
@table @code
@item (ses-select @var{fromrange} @var{test} @var{torange})
Returns a subset of @var{torange}. For each member in @var{fromrange}
that is equal to @var{test}, the corresponding member of @var{torange}
is included in the result.
@end table
Example of use:
@lisp
(ses-average (ses-select (ses-range A1 A5) 'Smith (ses-range B1 B5)))
@end lisp
This computes the average of the B column values for those rows whose
A column value is the symbol 'Smith.
Arguably one could specify only @var{fromrange} plus
@var{to-row-offset} and @var{to-column-offset}. The @var{torange} is
stated explicitly to ensure that the formula will be recalculated if
any cell in either range is changed.
File @file{etc/ses-example.el} in the Emacs distribution is an example of a
details-and-summary spreadsheet.
@c ===================================================================
@node For Gurus, Acknowledgements, Advanced Features, Top
@chapter For Gurus
@menu
* Deferred updates::
* Nonrelocatable references::
* The data area::
* Buffer-local variables in spreadsheets::
* Uses of defadvice in SES::
@end menu
@node Deferred updates, Nonrelocatable references, For Gurus, For Gurus
@section Deferred updates
To save time by avoiding redundant computations, cells that need
recalculation due to changes in other cells are added to a set. At
the end of the command, each cell in the set is recalculated once.
This can create a new set of cells that need recalculation. The
process is repeated until either the set is empty or it stops changing
(due to circular references among the cells). In extreme cases, you
might see progress messages of the form ``Recalculating... (@var{nnn}
cells left)''. If you interrupt the calculation using @kbd{C-g}, the
spreadsheet will be left in an inconsistent state, so use @kbd{C-_} or
@kbd{C-c C-l} to fix it.
To save even more time by avoiding redundant writes, cells that have
changes are added to a set instead of being written immediately to the
data area. Each cell in the set is written once, at the end of the
command. If you change vast quantities of cells, you might see a
progress message of the form ``Writing... (@var{nnn} cells left)''.
These deferred cell-writes cannot be interrupted by @kbd{C-g}, so
you'll just have to wait.
SES uses @code{run-with-idle-timer} to move the cell underline when
Emacs will be scrolling the buffer after the end of a command, and
also to narrow and underline after @kbd{C-x C-v}. This is visible as
a momentary glitch after C-x C-v and certain scrolling commands. You
can type ahead without worrying about the glitch.
@node Nonrelocatable references, The data area, Deferred updates, For Gurus
@section Nonrelocatable references
@kbd{C-y} relocates all cell-references in a pasted formula, while
@kbd{C-u C-y} relocates none of the cell-references. What about mixed
cases?
You can use
@lisp
(symbol-value 'B3)
@end lisp
to make an @dfn{absolute reference}. The formula relocator skips over
quoted things, so this will not be relocated when pasted or when
rows/columns are inserted/deleted. However, B3 will not be recorded
as a dependency of this cell, so this cell will not be updated
automatically when B3 is changed.
The variables @code{row} and @code{col} are dynamically bound while a
cell formula is being evaluated. You can use
@lisp
(ses-cell-value row 0)
@end lisp
to get the value from the leftmost column in the current row. This
kind of dependency is also not recorded.
@node The data area, Buffer-local variables in spreadsheets, Nonrelocatable references, For Gurus
@section The data area
Begins with an 014 character, followed by sets of cell-definition
macros for each row, followed by column-widths, column-printers,
default-printer, and header-row. Then there's the global parameters
(file-format ID, numrows, numcols) and the local variables (specifying
SES mode for the buffer, etc.)
When a SES file is loaded, first the numrows and numcols values are
loaded, then the entire data area is @code{eval}ed, and finally the local
variables are processed.
You can edit the data area, but don't insert or delete any newlines
except in the local-variables part, since SES locates things by
counting newlines. Use @kbd{C-x C-e} at the end of a line to install
your edits into the spreadsheet data structures (this does not update
the print area, use e.g. @kbd{C-c C-l} for that).
The data area is maintained as an image of spreadsheet data
structures that area stored in buffer-local variables. If the data
area gets messed up, you can try reconstructing the data area from the
data structures:
@table @kbd
@item C-c M-C-l
(@code{ses-reconstruct-all}).
@end table
@node Buffer-local variables in spreadsheets, Uses of defadvice in SES, The data area, For Gurus
@section Buffer-local variables in spreadsheets
You can add additional local variables to the list at the bottom of
the data area, such as hidden constants you want to refer to in your
formulas.
You can override the variable @code{symbolic-formulas} to be a list of
symbols (as parenthesized strings) to show as completions for the '
command. This initial completions list is used instead of the actual
set of symbols-as-formulas in the spreadsheet.
For examples of these, see file @file{etc/ses-example.ses}.
If (for some reason) you want your formulas or printers to save data
into variables, you must declare these variables as buffer-locals in
order to avoid a virus warning.
You can define functions by making them values for the fake local
variable @code{eval}. Such functions can then be used in your
formulas and printers, but usually each @code{eval} is presented to
the user during file loading as a potential virus --- this can get
annoying.
You can define functions in your @file{.emacs} file. Other people can
still read the print area of your spreadsheet, but they won't be able
to recalculate or reprint anything that depends on your functions. To
avoid virus warnings, each function used in a formula needs
@lisp
(put 'your-function-name 'safe-function t)
@end lisp
@node Uses of defadvice in SES, , Buffer-local variables in spreadsheets, For Gurus
@section Uses of defadvice in SES
@table @code
@item undo-more
Defines a new undo element format (@var{fun} . @var{args}), which
means ``undo by applying @var{fun} to @var{args}''. For spreadsheet
buffers, it allows undos in the data area even though that's outside
the narrowing.
@item copy-region-as-kill
When copying from the print area of a spreadsheet, treat the region as
a rectangle and attach each cell's formula and printer as 'ses
properties.
@item yank
When yanking into the print area of a spreadsheet, first try to yank
as cells (if the yank text has 'ses properties), then as tab-separated
formulas, then (if all else fails) as a single formula for the current
cell.
@end table
@c ===================================================================
@node Acknowledgements, , For Gurus, Top
@chapter Acknowledgements
@quotation
Christoph Conrad @email{christoph.conrad@@gmx.de}@*
CyberBob @email{cyberbob@@redneck.gacracker.org}@*
Syver Enstad @email{syver-en@@online.no}@*
Ami Fischman @email{fischman@@zion.bpnetworks.com}@*
Thomas Gehrlein @email{Thomas.Gehrlein@@t-online.de}@*
Chris F.A. Johnson @email{c.f.a.johnson@@rogers.com}@*
Yusong Li @email{lyusong@@hotmail.com}@*
Yuri Linkov @email{link0ff@@yahoo.com}@*
Harald Maier @email{maierh@@myself.com}@*
Alan Nash @email{anash@@san.rr.com}@*
François Pinard @email{pinard@@iro.umontreal.ca}@*
Pedro Pinto @email{ppinto@@cs.cmu.edu}@*
Stefan Reichör @email{xsteve@@riic.at}@*
Oliver Scholz @email{epameinondas@@gmx.de}@*
Richard M. Stallman @email{rms@@gnu.org}@*
J. Otto Tennant @email{jotto@@pobox.com}@*
Jean-Philippe Theberge @email{jphil@@acs.pagesjaunes.fr}
@end quotation
@c ===================================================================
@bye