* admin/grammars/Makefile.in (bootstrap-clean): Don't delete Makefile,
[bpt/emacs.git] / lisp / ses.el
index 9b2048e..a4f5609 100644 (file)
@@ -1,6 +1,6 @@
-;;; ses.el -- Simple Emacs Spreadsheet  -*- coding: utf-8 -*-
+;;; ses.el -- Simple Emacs Spreadsheet  -*- lexical-binding:t -*-
 
-;; Copyright (C) 2002-201 Free Software Foundation, Inc.
+;; Copyright (C) 2002-2014 Free Software Foundation, Inc.
 
 ;; Author: Jonathan Yavner <jyavner@member.fsf.org>
 ;; Maintainer: Vincent Belaïche  <vincentb1@users.sourceforge.net>
@@ -43,7 +43,7 @@
 ;; working fine in most cases, however failed in some cases of several path
 ;; racing together.
 ;;
-;; The current algorithm is based on Dijksta algorithm.  The ``cycle length'' is
+;; The current algorithm is based on Dijkstra's algorithm.  The cycle length is
 ;; stored in some cell property. In order not to reset in all cells such
 ;; property at each update, the cycle length is stored in this property along
 ;; with some update attempt id that is incremented at each update. The current
@@ -56,7 +56,7 @@
 ;;; Code:
 
 (require 'unsafep)
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl-lib))
 
 
 ;;----------------------------------------------------------------------------
@@ -65,7 +65,9 @@
 
 (defgroup ses nil
   "Simple Emacs Spreadsheet."
+  :tag "SES"
   :group  'applications
+  :link '(custom-manual "(ses) Top")
   :prefix "ses-"
   :version "21.1")
 
@@ -237,6 +239,10 @@ Each function is called with ARG=1."
   "\n( ;Global parameters (these are read first)\n 2 ;SES file-format\n 1 ;numrows\n 1 ;numcols\n)\n\n"
   "Initial contents for the three-element list at the bottom of the data area.")
 
+(defconst ses-initial-global-parameters-re
+  "\n( ;Global parameters (these are read first)\n [23] ;SES file-format\n [0-9]+ ;numrows\n [0-9]+ ;numcols\n\\( [0-9]+ ;numlocprn\n\\)?)\n\n"
+  "Match Global parameters for .")
+
 (defconst ses-initial-file-trailer
   ";; Local Variables:\n;; mode: ses\n;; End:\n"
   "Initial contents for the file-trailer area at the bottom of the file.")
@@ -275,17 +281,27 @@ default printer and then modify its output.")
     '(ses--blank-line ses--cells ses--col-printers
       ses--col-widths ses--curcell ses--curcell-overlay
       ses--default-printer
+      (ses--local-printer-hashmap . :hashmap)
+      (ses--numlocprn . 0); count of local printers
       ses--deferred-narrow ses--deferred-recalc
       ses--deferred-write ses--file-format
+      ses--named-cell-hashmap
       (ses--header-hscroll . -1) ; Flag for "initial recalc needed"
       ses--header-row ses--header-string ses--linewidth
       ses--numcols ses--numrows ses--symbolic-formulas
       ses--data-marker ses--params-marker (ses--Dijkstra-attempt-nb . 0)
       ses--Dijkstra-weight-bound
+      ;; This list is useful to speed-up clean-up of symbols when
+      ;; an area containing renamed cell is deleted.
+      ses--renamed-cell-symb-list
       ;; Global variables that we override
       mode-line-process next-line-add-newlines transient-mark-mode)
-    "Buffer-local variables used by SES.")
+    "Buffer-local variables used by SES."))
 
+(defmacro ses--metaprogramming (exp) (declare (debug t)) (eval exp t))
+(ses--metaprogramming
+ `(progn ,@(mapcar (lambda (x) `(defvar ,(or (car-safe x) x))) ses-localvars)))
 (defun ses-set-localvars ()
   "Set buffer-local and initialize some SES variables."
   (dolist (x ses-localvars)
@@ -293,11 +309,14 @@ default printer and then modify its output.")
      ((symbolp x)
       (set (make-local-variable x) nil))
      ((consp x)
-      (set (make-local-variable (car x)) (cdr x)))
-     (t (error "Unexpected elements `%S' in list `ses-localvars'" x))))))
-
-(eval-when-compile                     ; silence compiler
-  (ses-set-localvars))
+      (cond
+       ((integerp (cdr x))
+       (set (make-local-variable (car x)) (cdr x)))
+       ((eq (cdr x) :hashmap)
+       (set (make-local-variable (car x)) (make-hash-table :test 'eq)))
+       (t (error "Unexpected initializer `%S' in list `ses-localvars' for entry %S"
+                (cdr x) (car x)) )     ))
+     (t (error "Unexpected elements `%S' in list `ses-localvars'" x)))))
 
 ;;; This variable is documented as being permitted in file-locals:
 (put 'ses--symbolic-formulas 'safe-local-variable 'consp)
@@ -305,10 +324,21 @@ default printer and then modify its output.")
 (defconst ses-paramlines-plist
   '(ses--col-widths  -5 ses--col-printers -4 ses--default-printer -3
     ses--header-row  -2 ses--file-format   1 ses--numrows          2
-    ses--numcols      3)
+    ses--numcols      3 ses--numlocprn     4)
   "Offsets from 'Global parameters' line to various parameter lines in the
 data area of a spreadsheet.")
 
+(defconst ses-paramfmt-plist
+  '(ses--col-widths       "(ses-column-widths %S)"
+    ses--col-printers     "(ses-column-printers %S)"
+    ses--default-printer  "(ses-default-printer %S)"
+    ses--header-row       "(ses-header-row %S)"
+    ses--file-format      " %S ;SES file-format"
+    ses--numrows          " %S ;numrows"
+    ses--numcols          " %S ;numcols"
+    ses--numlocprn        " %S ;numlocprn")
+  "Formats of 'Global parameters' various parameters in the data
+area of a spreadsheet.")
 
 ;;
 ;;  "Side-effect variables".  They are set in one function, altered in
@@ -327,7 +357,7 @@ need to be recalculated.")
 
 (defvar ses-call-printer-return nil
   "Set to t if last cell printer invoked by `ses-call-printer' requested
-left-justification of the result.  Set to error-signal if ses-call-printer
+left-justification of the result.  Set to error-signal if `ses-call-printer'
 encountered an error during printing.  Otherwise nil.")
 
 (defvar ses-start-time nil
@@ -341,141 +371,115 @@ when to emit a progress message.")
 
 (defmacro ses-get-cell (row col)
   "Return the cell structure that stores information about cell (ROW,COL)."
+  (declare (debug t))
   `(aref (aref ses--cells ,row) ,col))
 
-;; We might want to use defstruct here, but cells are explicitly used as
-;; arrays in ses-set-cell, so we'd need to fix this first.  --Stef
-(defsubst ses-make-cell (&optional symbol formula printer references
-                                  property-list)
-  (vector symbol formula printer references property-list))
+(cl-defstruct (ses-cell
+              (:constructor nil)
+              (:constructor ses-make-cell
+               (&optional symbol formula printer references))
+              (:copier nil)
+              ;; This is treated as an 4-elem array in various places.
+              ;; Mostly in ses-set-cell.
+              (:type vector)           ;Not named.
+              (:conc-name ses-cell--))
+  symbol formula printer references properties)
+
+(cl-defstruct (ses--locprn
+               (:constructor)
+               (:constructor ses-make-local-printer-info
+                (def &optional (compiled (ses-local-printer-compile def))
+                     (number ses--numlocprn))))
+  def
+  compiled
+  number
+  local-printer-list)
 
 (defmacro ses-cell-symbol (row &optional col)
   "From a CELL or a pair (ROW,COL), get the symbol that names the local-variable holding its value.  (0,0) => A1."
-  `(aref ,(if col `(ses-get-cell ,row ,col) row) 0))
+  (declare (debug t))
+  `(ses-cell--symbol ,(if col `(ses-get-cell ,row ,col) row)))
 (put 'ses-cell-symbol 'safe-function t)
 
 (defmacro ses-cell-formula (row &optional col)
   "From a CELL or a pair (ROW,COL), get the function that computes its value."
-  `(aref ,(if col `(ses-get-cell ,row ,col) row) 1))
+  (declare (debug t))
+  `(ses-cell--formula ,(if col `(ses-get-cell ,row ,col) row)))
 
 (defmacro ses-cell-printer (row &optional col)
   "From a CELL or a pair (ROW,COL), get the function that prints its value."
-  `(aref ,(if col `(ses-get-cell ,row ,col) row) 2))
+  (declare (debug t))
+  `(ses-cell--printer ,(if col `(ses-get-cell ,row ,col) row)))
 
 (defmacro ses-cell-references (row &optional col)
   "From a CELL or a pair (ROW,COL), get the list of symbols for cells whose
 functions refer to its value."
-  `(aref ,(if col `(ses-get-cell ,row ,col) row) 3))
-
-(defun ses-cell-property-get-fun (property-name cell)
-  ;; To speed up property fetching, each time a property is found it is placed
-  ;; in the first position.  This way, after the first get, the full property
-  ;; list needs to be scanned only when the property does not exist for that
-  ;; cell.
-  (let* ((plist  (aref cell 4))
-        (ret (plist-member plist property-name)))
-    (if ret
-       ;; Property was found.
-       (let ((val (cadr ret)))
-         (if (eq ret plist)
-             ;; Property found is already in the first position, so just return
-             ;; its value.
-             val
-           ;; Property is not in the first position, the following will move it
-           ;; there before returning its value.
-           (let ((next (cddr ret)))
-             (if next
-                 (progn
-                   (setcdr ret (cdr next))
-                   (setcar ret (car next)))
-               (setcdr (last plist 1) nil)))
-           (aset cell 4
-                 `(,property-name ,val ,@plist))
-           val)))))
-
-(defmacro ses-cell-property-get (property-name row &optional col)
-   "Get property named PROPERTY-NAME From a CELL or a pair (ROW,COL).
+  (declare (debug t))
+  `(ses-cell--references ,(if col `(ses-get-cell ,row ,col) row)))
+
+(defun ses-cell-p (cell)
+  "Return non-nil if CELL is a cell of current buffer."
+  (and (vectorp cell)
+       (= (length cell) 5)
+       (eq cell (let ((rowcol (ses-sym-rowcol (ses-cell-symbol cell))))
+                 (and (consp rowcol)
+                      (ses-get-cell (car rowcol) (cdr rowcol)))))))
+
+
+(defun ses--alist-get (key alist &optional remove)
+  "Get the value associated to KEY in ALIST."
+  (declare
+   (gv-expander
+    (lambda (do)
+      (macroexp-let2 macroexp-copyable-p k key
+        (gv-letplace (getter setter) alist
+          (macroexp-let2 nil p `(assq ,k ,getter)
+            (funcall do `(cdr ,p)
+                     (lambda (v)
+                       (let ((set-exp
+                              `(if ,p (setcdr ,p ,v)
+                                 ,(funcall setter
+                                           `(cons (setq ,p (cons ,k ,v))
+                                                  ,getter)))))
+                         (cond
+                          ((null remove) set-exp)
+                          ((null v)
+                           `(if ,p ,(funcall setter `(delq ,p ,getter))))
+                          (t
+                           `(cond
+                             (,v ,set-exp)
+                             (,p ,(funcall setter
+                                           `(delq ,p ,getter)))))))))))))))
+  (ignore remove) ;;Silence byte-compiler.
+  (cdr (assoc key alist)))
+
+(defmacro ses--letref (vars place &rest body)
+  (declare (indent 2) (debug (sexp form &rest body)))
+  (gv-letplace (getter setter) place
+    `(cl-macrolet ((,(nth 0 vars) () ',getter)
+                   (,(nth 1 vars) (v) (funcall ,setter v)))
+       ,@body)))
+
+(defmacro ses-cell-property (property-name row &optional col)
+  "Get property named PROPERTY-NAME from a CELL or a pair (ROW,COL).
 
 When COL is omitted, CELL=ROW is a cell object.  When COL is
 present ROW and COL are the integer coordinates of the cell of
 interest."
-   (declare (debug t))
-   `(ses-cell-property-get-fun
-     ,property-name
-     ,(if col `(ses-get-cell ,row ,col) row)))
-
-(defun ses-cell-property-delq-fun (property-name cell)
-  (let ((ret (plist-get (aref cell 4) property-name)))
-    (if ret
-      (setcdr ret (cddr ret)))))
-
-(defun ses-cell-property-set-fun (property-name property-val cell)
-  (let*        ((plist  (aref cell 4))
-        (ret (plist-member plist property-name)))
-    (if ret
-       (setcar (cdr ret) property-val)
-      (aset cell 4 `(,property-name ,property-val ,@plist)))))
-
-(defmacro ses-cell-property-set (property-name property-value row &optional col)
-   "From a CELL or a pair (ROW,COL), set the property value of
-the corresponding cell with name PROPERTY-NAME to PROPERTY-VALUE."
-   (if property-value
-       `(ses-cell-property-set-fun ,property-name ,property-value
-                                  ,(if col `(ses-get-cell ,row ,col) row))
-       `(ses-cell-property-delq-fun ,property-name
-                                   ,(if col `(ses-get-cell ,row ,col) row))))
-
-(defun ses-cell-property-pop-fun (property-name cell)
-  (let* ((plist  (aref cell 4))
-        (ret (plist-member plist property-name)))
-    (if ret
-       (prog1 (cadr ret)
-         (let ((next (cddr ret)))
-           (if next
-               (progn
-                 (setcdr ret (cdr next))
-                 (setcar ret (car next)))
-             (if (eq plist ret)
-                 (aset cell 4 nil)
-               (setcdr (last plist 2) nil))))))))
-
+  (declare (debug t))
+  `(ses--alist-get ,property-name
+                   (ses-cell--properties
+                    ,(if col `(ses-get-cell ,row ,col) row))))
 
 (defmacro ses-cell-property-pop (property-name row &optional col)
-   "From a CELL or a pair (ROW,COL), get and remove the property value of
+  "From a CELL or a pair (ROW,COL), get and remove the property value of
 the corresponding cell with name PROPERTY-NAME."
-   `(ses-cell-property-pop-fun  ,property-name
-                               ,(if col `(ses-get-cell ,row ,col) row)))
-
-(defun ses-cell-property-get-handle-fun (property-name cell)
-  (let*        ((plist  (aref cell 4))
-        (ret (plist-member plist property-name)))
-    (if ret
-       (if (eq ret plist)
-           (cdr ret)
-         (let ((val (cadr ret))
-               (next (cddr ret)))
-           (if next
-               (progn
-                 (setcdr ret (cdr next))
-                 (setcar ret (car next)))
-             (setcdr (last plist 2) nil))
-           (setq ret (cons val plist))
-           (aset cell 4 (cons property-name ret))
-           ret))
-      (setq ret (cons nil plist))
-      (aset cell 4 (cons property-name ret))
-      ret)))
-
-(defmacro ses-cell-property-get-handle (property-name row &optional col)
-   "From a CELL or a pair (ROW,COL), get a cons cell whose car is
-the property value of the corresponding cell property with name
-PROPERTY-NAME."
-   `(ses-cell-property-get-handle-fun  ,property-name
-                               ,(if col `(ses-get-cell ,row ,col) row)))
-
-
-(defalias 'ses-cell-property-handle-car 'car)
-(defalias 'ses-cell-property-handle-setcar 'setcar)
+  `(ses--letref (pget pset)
+       (ses--alist-get ,property-name
+                       (ses-cell--properties
+                        ,(if col `(ses-get-cell ,row ,col) row))
+                       t)
+     (prog1 (pget) (pset nil))))
 
 (defmacro ses-cell-value (row &optional col)
   "From a CELL or a pair (ROW,COL), get the current value for that cell."
@@ -492,16 +496,29 @@ PROPERTY-NAME."
 (defmacro ses-sym-rowcol (sym)
   "From a cell-symbol SYM, gets the cons (row . col).  A1 => (0 . 0).  Result
 is nil if SYM is not a symbol that names a cell."
-  `(and (symbolp ,sym) (get ,sym 'ses-cell)))
-
-(defmacro ses-cell (sym value formula printer references)
+  `(let ((rc (and (symbolp ,sym) (get ,sym 'ses-cell))))
+     (if (eq rc :ses-named)
+        (gethash ,sym ses--named-cell-hashmap)
+       rc)))
+
+(defun ses-is-cell-sym-p (sym)
+  "Check whether SYM point at a cell of this spread sheet."
+  (let ((rowcol (get sym 'ses-cell)))
+    (and rowcol
+        (if (eq rowcol :ses-named)
+            (and ses--named-cell-hashmap (gethash sym ses--named-cell-hashmap))
+          (and (< (car rowcol) ses--numrows)
+               (< (cdr rowcol) ses--numcols)
+               (eq (ses-cell-symbol (car rowcol) (cdr rowcol)) sym))))))
+
+(defun ses--cell (sym value formula printer references)
   "Load a cell SYM from the spreadsheet file.  Does not recompute VALUE from
-FORMULA, does not reprint using PRINTER, does not check REFERENCES.  This is a
-macro to prevent propagate-on-load viruses.  Safety-checking for FORMULA and
-PRINTER are deferred until first use."
+FORMULA, does not reprint using PRINTER, does not check REFERENCES.
+Safety-checking for FORMULA and PRINTER are deferred until first use."
   (let ((rowcol (ses-sym-rowcol sym)))
     (ses-formula-record formula)
     (ses-printer-record printer)
+    (unless formula (setq formula value))
     (or (atom formula)
        (eq safe-functions t)
        (setq formula `(ses-safe-formula ,formula)))
@@ -509,11 +526,32 @@ PRINTER are deferred until first use."
        (stringp printer)
        (eq safe-functions t)
        (setq printer `(ses-safe-printer ,printer)))
-    (aset (aref ses--cells (car rowcol))
-         (cdr rowcol)
+    (setf (ses-get-cell (car rowcol) (cdr rowcol))
          (ses-make-cell sym formula printer references)))
-  (set sym value)
-  sym)
+  (set sym value))
+
+(defun ses-local-printer-compile (printer)
+  "Convert local printer function into faster printer
+definition."
+  (cond
+   ((functionp printer) printer)
+   ((stringp printer)
+    `(lambda (x) (format ,printer x)))
+   (t (error "Invalid printer %S" printer))))
+
+(defun ses--local-printer (name def)
+  "Define a local printer with name NAME and definition DEF.
+Return the printer info."
+  (or
+   (and (symbolp name)
+       (ses-printer-validate def))
+   (error "Invalid local printer definition"))
+  (and (gethash name ses--local-printer-hashmap)
+       (error "Duplicate printer definition %S" name))
+  (add-to-list 'ses-read-printer-history (symbol-name name))
+  (puthash name
+          (ses-make-local-printer-info (ses-safe-printer def))
+          ses--local-printer-hashmap))
 
 (defmacro ses-column-widths (widths)
   "Load the vector of column widths from the spreadsheet file.  This is a
@@ -583,9 +621,11 @@ variables `minrow', `maxrow', `mincol', and `maxcol'."
 (defmacro 1value (form)
   "For code-coverage testing, indicate that FORM is expected to always have
 the same value."
+  (declare (debug t))
   form)
 (defmacro noreturn (form)
   "For code-coverage testing, indicate that FORM will always signal an error."
+  (declare (debug t))
   form)
 
 
@@ -625,12 +665,14 @@ is a vector--if a symbol, the new vector is assigned as the symbol's value."
     (delete-region pos (point))))
 
 (defun ses-printer-validate (printer)
-  "Signals an error if PRINTER is not a valid SES cell printer."
+  "Signal an error if PRINTER is not a valid SES cell printer."
   (or (not printer)
       (stringp printer)
+      ;; printer is a local printer
+      (and (symbolp printer) (gethash printer ses--local-printer-hashmap))
       (functionp printer)
       (and (stringp (car-safe printer)) (not (cdr printer)))
-      (error "Invalid printer function"))
+      (error "Invalid printer function %S" printer))
   printer)
 
 (defun ses-printer-record (printer)
@@ -642,7 +684,7 @@ checking that it is a valid printer function."
       (add-to-list 'ses-read-printer-history (prin1-to-string printer))))
 
 (defun ses-formula-record (formula)
-  "If FORMULA is of the form 'symbol, adds it to the list of symbolic formulas
+  "If FORMULA is of the form 'symbol, add it to the list of symbolic formulas
 for this spreadsheet."
   (when (and (eq (car-safe formula) 'quote)
             (symbolp (cadr formula)))
@@ -661,6 +703,29 @@ for this spreadsheet."
   "Produce a symbol that names the cell (ROW,COL).  (0,0) => 'A1."
   (intern (concat (ses-column-letter col) (number-to-string (1+ row)))))
 
+(defun ses-decode-cell-symbol (str)
+  "Decode a symbol \"A1\" => (0,0).  Return nil if STR is not a
+canonical cell name."
+  (let (case-fold-search)
+    (and (string-match "\\`\\([A-Z]+\\)\\([0-9]+\\)\\'" str)
+        (let* ((col-str (match-string-no-properties 1 str))
+                (col 0)
+                (col-base 1)
+                (col-idx (1- (length col-str)))
+                (row (1- (string-to-number
+                          (match-string-no-properties 2 str)))))
+          (and (>= row 0)
+               (progn
+                 (while
+                     (progn
+                       (setq col (+ col (* (- (aref col-str col-idx) ?A)
+                                            col-base))
+                             col-base (* col-base 26)
+                             col-idx (1- col-idx))
+                       (and (>= col-idx 0)
+                            (setq col (+ col col-base)))))
+                 (cons row col)))))))
+
 (defun ses-create-cell-variable-range (minrow maxrow mincol maxcol)
   "Create buffer-local variables for cells.  This is undoable."
   (push `(apply ses-destroy-cell-variable-range ,minrow ,maxrow ,mincol ,maxcol)
@@ -674,6 +739,21 @@ for this spreadsheet."
        (put sym 'ses-cell (cons xrow xcol))
        (make-local-variable sym)))))
 
+(defun ses-create-cell-variable (sym row col)
+  "Create a buffer-local variable `SYM' for cell at position (ROW, COL).
+
+SYM is the symbol for that variable, ROW and COL are integers for
+row and column of the cell, with numbering starting from 0.
+
+Return nil in case of failure."
+  (unless (local-variable-p sym)
+    (make-local-variable  sym)
+    (if (let (case-fold-search) (string-match-p "\\`[A-Z]+[0-9]+\\'" (symbol-name sym)))
+       (put sym 'ses-cell (cons row col))
+      (put sym 'ses-cell :ses-named)
+      (setq ses--named-cell-hashmap (or ses--named-cell-hashmap (make-hash-table :test 'eq)))
+      (puthash sym (cons row col) ses--named-cell-hashmap))))
+
 ;; We do not delete the ses-cell properties for the cell-variables, in
 ;; case a formula that refers to this cell is in the kill-ring and is
 ;; later pasted back in.
@@ -682,7 +762,10 @@ for this spreadsheet."
   (let (sym)
     (dotimes (row (1+ (- maxrow minrow)))
       (dotimes (col (1+ (- maxcol mincol)))
-       (setq sym (ses-create-cell-symbol (+ row minrow) (+ col mincol)))
+       (let ((xrow  (+ row minrow)) (xcol (+ col mincol)))
+         (setq sym (if (and (< xrow ses--numrows) (< xcol ses--numcols))
+                       (ses-cell-symbol xrow xcol)
+                       (ses-create-cell-symbol xrow xcol))))
        (if (boundp sym)
            (push `(apply ses-set-with-undo ,sym ,(symbol-value sym))
                  buffer-undo-list))
@@ -691,7 +774,7 @@ for this spreadsheet."
        buffer-undo-list))
 
 (defun ses-reset-header-string ()
-  "Flags the header string for update.  Upon undo, the header string will be
+  "Flag the header string for update.  Upon undo, the header string will be
 updated again."
   (push '(apply ses-reset-header-string) buffer-undo-list)
   (setq ses--header-hscroll -1))
@@ -710,24 +793,37 @@ and (eval ARG) and reset `ses-start-time' to the current time."
 ;; The cells
 ;;----------------------------------------------------------------------------
 
-(defun ses-set-cell (row col field val)
+(defmacro ses-set-cell (row col field val)
   "Install VAL as the contents for field FIELD (named by a quoted symbol) of
 cell (ROW,COL).  This is undoable.  The cell's data will be updated through
 `post-command-hook'."
-  (let ((cell (ses-get-cell row col))
-       (elt  (plist-get '(value t symbol 0 formula 1 printer 2 references 3)
-                        field))
-       change)
-    (or elt (signal 'args-out-of-range nil))
-    (setq change (if (eq elt t)
-                    (ses-set-with-undo (ses-cell-symbol cell) val)
-                  (ses-aset-with-undo cell elt val)))
-    (if change
-       (add-to-list 'ses--deferred-write (cons row col))))
-  nil) ; Make coverage-tester happy.
+  `(let ((row ,row)
+         (col ,col)
+         (val ,val))
+     (let* ((cell (ses-get-cell row col))
+            (change
+             ,(let ((field (eval field t)))
+                (if (eq field 'value)
+                    `(ses-set-with-undo (ses-cell-symbol cell) val)
+                  ;; (let* ((slots (get 'ses-cell 'cl-struct-slots))
+                  ;;        (slot (or (assq field slots)
+                  ;;                  (error "Unknown field %S" field)))
+                  ;;        (idx (- (length slots)
+                  ;;                (length (memq slot slots)))))
+                  ;;   `(ses-aset-with-undo cell ,idx val))
+                  (let ((getter (intern-soft (format "ses-cell--%s" field))))
+                    `(ses-setter-with-undo
+                      (eval-when-compile
+                        (cons #',getter
+                              (lambda (newval cell)
+                                (setf (,getter cell) newval))))
+                      val cell))))))
+       (if change
+           (add-to-list 'ses--deferred-write (cons row col))))
+     nil)) ; Make coverage-tester happy.
 
 (defun ses-cell-set-formula (row col formula)
-  "Store a new formula for (ROW . COL) and enqueues the cell for
+  "Store a new formula for (ROW . COL) and enqueue the cell for
 recalculation via `post-command-hook'.  Updates the reference lists for the
 cells that this cell refers to.  Does not update cell value or reprint the
 cell.  To avoid inconsistencies, this function is not interruptible, which
@@ -739,7 +835,7 @@ means Emacs will crash if FORMULA contains a circular list."
          (newref (ses-formula-references formula))
          (inhibit-quit t)
          x xrow xcol)
-      (add-to-list 'ses--deferred-recalc sym)
+      (cl-pushnew sym ses--deferred-recalc)
       ;;Delete old references from this cell.  Skip the ones that are also
       ;;in the new list.
       (dolist (ref oldref)
@@ -770,11 +866,11 @@ means Emacs will crash if FORMULA contains a circular list."
       (dotimes (col ses--numcols)
        (let ((references  (ses-cell-property-pop :ses-repair-reference
                                                  row col)))
-       (when references
-         (push (list
-                (ses-cell-symbol row col)
-                :corrupt-property
-                references) errors)))))
+          (when references
+            (push (list (ses-cell-symbol row col)
+                        :corrupt-property
+                        references)
+                  errors)))))
 
     ;; Step 2, build new.
     (dotimes (row ses--numrows)
@@ -784,21 +880,17 @@ means Emacs will crash if FORMULA contains a circular list."
               (formula (ses-cell-formula cell))
               (new-ref (ses-formula-references formula)))
          (dolist (ref new-ref)
-           (let* ((rowcol (ses-sym-rowcol ref))
-                 (h (ses-cell-property-get-handle :ses-repair-reference
-                                                 (car rowcol) (cdr rowcol))))
-             (unless (memq ref (ses-cell-property-handle-car h))
-               (ses-cell-property-handle-setcar
-                h
-                (cons sym
-                      (ses-cell-property-handle-car h)))))))))
+           (let ((rowcol (ses-sym-rowcol ref)))
+              (cl-pushnew sym (ses-cell-property :ses-repair-reference
+                                                 (car rowcol)
+                                                 (cdr rowcol))))))))
 
     ;; Step 3, overwrite with check.
     (dotimes (row ses--numrows)
       (dotimes (col ses--numcols)
        (let* ((cell (ses-get-cell row col))
               (irrelevant (ses-cell-references cell))
-              (new-ref (ses-cell-property-pop  :ses-repair-reference cell))
+              (new-ref (ses-cell-property-pop :ses-repair-reference cell))
               missing)
          (dolist (ref new-ref)
            (if (memq ref irrelevant)
@@ -811,10 +903,10 @@ means Emacs will crash if FORMULA contains a circular list."
                     ,@(and irrelevant  (list :irrelevant irrelevant)))
                  errors)))))
     (if errors
-      (warn "----------------------------------------------------------------
-Some reference where corrupted.
+        (warn "----------------------------------------------------------------
+Some references were corrupted.
 
-The following is a list of where each element ELT is such
+The following is a list where each element ELT is such
 that (car ELT) is the reference of cell CELL with corruption,
 and (cdr ELT) is a property list where
 
@@ -842,12 +934,7 @@ the old and FORCE is nil."
     (let ((oldval  (ses-cell-value   cell))
          (formula (ses-cell-formula cell))
          newval
-         this-cell-Dijkstra-attempt-h
-         this-cell-Dijkstra-attempt
-         this-cell-Dijkstra-attempt+1
-         ref-cell-Dijkstra-attempt-h
-         ref-cell-Dijkstra-attempt
-         ref-rowcol)
+         this-cell-Dijkstra-attempt+1)
       (when (eq (car-safe formula) 'ses-safe-formula)
        (setq formula (ses-safe-formula (cadr formula)))
        (ses-set-cell row col 'formula formula))
@@ -863,46 +950,42 @@ the old and FORCE is nil."
          (setq newval '*skip*))
       (catch 'cycle
        (when (or force (not (eq newval oldval)))
-         (add-to-list 'ses--deferred-write (cons row col)) ; In case force=t.
-         (setq this-cell-Dijkstra-attempt-h
-               (ses-cell-property-get-handle :ses-Dijkstra-attempt cell);
-               this-cell-Dijkstra-attempt
-               (ses-cell-property-handle-car this-cell-Dijkstra-attempt-h))
-         (if (null this-cell-Dijkstra-attempt)
-             (ses-cell-property-handle-setcar
-              this-cell-Dijkstra-attempt-h
-              (setq this-cell-Dijkstra-attempt
-                    (cons ses--Dijkstra-attempt-nb 0)))
-           (unless (= ses--Dijkstra-attempt-nb
-                      (car this-cell-Dijkstra-attempt))
-               (setcar this-cell-Dijkstra-attempt ses--Dijkstra-attempt-nb)
-               (setcdr this-cell-Dijkstra-attempt 0)))
-         (setq this-cell-Dijkstra-attempt+1
-               (1+ (cdr this-cell-Dijkstra-attempt)))
+         (cl-pushnew (cons row col) ses--deferred-write :test #'equal) ; In case force=t.
+          (ses--letref (pget pset)
+              (ses-cell-property :ses-Dijkstra-attempt cell)
+            (let ((this-cell-Dijkstra-attempt (pget)))
+              (if (null this-cell-Dijkstra-attempt)
+                  (pset
+                   (setq this-cell-Dijkstra-attempt
+                         (cons ses--Dijkstra-attempt-nb 0)))
+                (unless (= ses--Dijkstra-attempt-nb
+                           (car this-cell-Dijkstra-attempt))
+                  (setcar this-cell-Dijkstra-attempt ses--Dijkstra-attempt-nb)
+                  (setcdr this-cell-Dijkstra-attempt 0)))
+              (setq this-cell-Dijkstra-attempt+1
+                    (1+ (cdr this-cell-Dijkstra-attempt)))))
          (ses-set-cell row col 'value newval)
          (dolist (ref (ses-cell-references cell))
-           (add-to-list 'ses--deferred-recalc ref)
-           (setq ref-rowcol (ses-sym-rowcol ref)
-                 ref-cell-Dijkstra-attempt-h
-                 (ses-cell-property-get-handle
-                  :ses-Dijkstra-attempt
-                  (car ref-rowcol) (cdr ref-rowcol))
-                 ref-cell-Dijkstra-attempt
-                 (ses-cell-property-handle-car ref-cell-Dijkstra-attempt-h))
-
-           (if (null ref-cell-Dijkstra-attempt)
-             (ses-cell-property-handle-setcar
-              ref-cell-Dijkstra-attempt-h
-              (setq ref-cell-Dijkstra-attempt
-                     (cons ses--Dijkstra-attempt-nb
-                           this-cell-Dijkstra-attempt+1)))
-             (if (= (car ref-cell-Dijkstra-attempt) ses--Dijkstra-attempt-nb)
-                 (setcdr ref-cell-Dijkstra-attempt
-                         (max (cdr ref-cell-Dijkstra-attempt)
-                              this-cell-Dijkstra-attempt+1))
-               (setcar ref-cell-Dijkstra-attempt ses--Dijkstra-attempt-nb)
-               (setcdr ref-cell-Dijkstra-attempt
-                       this-cell-Dijkstra-attempt+1)))
+           (cl-pushnew ref ses--deferred-recalc)
+            (ses--letref (pget pset)
+                (let ((ref-rowcol (ses-sym-rowcol ref)))
+                  (ses-cell-property
+                   :ses-Dijkstra-attempt
+                   (car ref-rowcol) (cdr ref-rowcol)))
+              (let ((ref-cell-Dijkstra-attempt (pget)))
+
+                (if (null ref-cell-Dijkstra-attempt)
+                    (pset
+                     (setq ref-cell-Dijkstra-attempt
+                           (cons ses--Dijkstra-attempt-nb
+                                 this-cell-Dijkstra-attempt+1)))
+                  (if (= (car ref-cell-Dijkstra-attempt) ses--Dijkstra-attempt-nb)
+                      (setcdr ref-cell-Dijkstra-attempt
+                              (max (cdr ref-cell-Dijkstra-attempt)
+                                   this-cell-Dijkstra-attempt+1))
+                    (setcar ref-cell-Dijkstra-attempt ses--Dijkstra-attempt-nb)
+                    (setcdr ref-cell-Dijkstra-attempt
+                            this-cell-Dijkstra-attempt+1)))))
 
            (when (> this-cell-Dijkstra-attempt+1 ses--Dijkstra-weight-bound)
              ;; Update print of this cell.
@@ -922,8 +1005,8 @@ the old and FORCE is nil."
   (ses-cell-set-formula row col nil))
 
 (defcustom ses-self-reference-early-detection nil
-  "True if cycle detection is early for cells that refer to
-themselves."
+  "True if cycle detection is early for cells that refer to themselves."
+  :version "24.1"
   :type 'boolean
   :group 'ses)
 
@@ -961,7 +1044,7 @@ if the cell's value is unchanged and FORCE is nil."
                    (when (or (memq ref curlist)
                              (memq ref ses--deferred-recalc))
                      ;; This cell refers to another that isn't done yet
-                     (add-to-list 'ses--deferred-recalc this-sym)
+                     (cl-pushnew this-sym ses--deferred-recalc :test #'equal)
                      (throw 'ref t)))))
              ;; ses-update-cells is called from post-command-hook, so
              ;; inhibit-quit is implicitly bound to t.
@@ -970,7 +1053,7 @@ if the cell's value is unchanged and FORCE is nil."
                (error "Quit"))
              (ses-calculate-cell (car this-rowcol) (cdr this-rowcol) force)))
        (dolist (ref ses--deferred-recalc)
-         (add-to-list 'nextlist ref)))
+          (cl-pushnew ref nextlist :test #'equal)))
       (when ses--deferred-recalc
        ;; Just couldn't finish these.
        (dolist (x ses--deferred-recalc)
@@ -980,7 +1063,7 @@ if the cell's value is unchanged and FORCE is nil."
        (error "Circular references: %s" ses--deferred-recalc))
       (message " "))
     ;; Can't use save-excursion here: if the cell under point is updated,
-    ;; save-excusion's marker will move past the cell.
+    ;; save-excursion's marker will move past the cell.
     (goto-char pos)))
 
 
@@ -989,7 +1072,7 @@ if the cell's value is unchanged and FORCE is nil."
 ;;----------------------------------------------------------------------------
 
 (defun ses-in-print-area ()
-  "Returns t if point is in print area of spreadsheet."
+  "Return t if point is in print area of spreadsheet."
   (<= (point) ses--data-marker))
 
 ;; We turn off point-motion-hooks and explicitly position the cursor, in case
@@ -1011,7 +1094,7 @@ if the cell's value is unchanged and FORCE is nil."
         (forward-char))))
 
 (defun ses-set-curcell ()
-  "Sets `ses--curcell' to the current cell symbol, or a cons (BEG,END) for a
+  "Set `ses--curcell' to the current cell symbol, or a cons (BEG,END) for a
 region, or nil if cursor is not at a cell."
   (if (or (not mark-active)
          deactivate-mark
@@ -1030,10 +1113,10 @@ region, or nil if cursor is not at a cell."
   nil)
 
 (defun ses-check-curcell (&rest args)
-  "Signal an error if ses--curcell is inappropriate.  The end marker is
-appropriate if some argument is 'end.  A range is appropriate if some
-argument is 'range.  A single cell is appropriate unless some argument is
-'needrange."
+  "Signal an error if `ses--curcell' is inappropriate.
+The end marker is appropriate if some argument is 'end.
+A range is appropriate if some argument is 'range.
+A single cell is appropriate unless some argument is 'needrange."
   (if (eq ses--curcell t)
       ;; curcell recalculation was postponed, but user typed ahead.
       (ses-set-curcell))
@@ -1089,7 +1172,8 @@ preceding cell has spilled over."
         ((< len width)
          ;; Fill field to length with spaces.
          (setq len  (make-string (- width len) ?\s)
-               text (if (eq ses-call-printer-return t)
+               text (if (or (stringp value)
+                            (eq ses-call-printer-return t))
                         (concat text len)
                       (concat len text))))
         ((> len width)
@@ -1143,7 +1227,7 @@ preceding cell has spilled over."
        (setq x (concat text (if (< maxcol ses--numcols) " " "\n")))
        ;; We use set-text-properties to prevent a wacky print function from
        ;; inserting rogue properties, and to ensure that the keymap property is
-       ;; inherited (is it a bug that only unpropertied strings actually
+       ;; inherited (is it a bug that only unpropertized strings actually
        ;; inherit from surrounding text?)
        (set-text-properties 0 (length x) nil x)
        (insert-and-inherit x)
@@ -1168,7 +1252,7 @@ preceding cell has spilled over."
       sig)))
 
 (defun ses-call-printer (printer &optional value)
-  "Invokes PRINTER (a string or parenthesized string or function-symbol or
+  "Invoke PRINTER (a string or parenthesized string or function-symbol or
 lambda of one argument) on VALUE.  Result is the printed cell as a string.
 The variable `ses-call-printer-return' is set to t if the printer used
 parenthesis to request left-justification, or the error-signal if the
@@ -1186,7 +1270,13 @@ printer signaled one (and \"%s\" is used as the default printer), else nil."
            (format (car printer) value)
          ""))
        (t
-       (setq value (funcall printer (or value "")))
+       (setq value  (funcall
+                     (or (and (symbolp printer)
+                              (let ((locprn (gethash printer ses--local-printer-hashmap)))
+                                (and locprn
+                                     (ses--locprn-compiled locprn))))
+                         printer)
+                     (or value "")))
        (if (stringp value)
            value
          (or (stringp (car-safe value))
@@ -1200,7 +1290,7 @@ printer signaled one (and \"%s\" is used as the default printer), else nil."
 (defun ses-adjust-print-width (col change)
   "Insert CHANGE spaces in front of column COL, or at end of line if
 COL=NUMCOLS.  Deletes characters if CHANGE < 0.  Caller should bind
-inhibit-quit to t."
+`inhibit-quit' to t."
   (let ((inhibit-read-only t)
        (blank  (if (> change 0) (make-string change ?\s)))
        (at-end (= col ses--numcols)))
@@ -1219,9 +1309,9 @@ inhibit-quit to t."
        (delete-char (- change))))))
 
 (defun ses-print-cell-new-width (row col)
-  "Same as ses-print-cell, except if the cell's value is *skip*, the preceding
-nonskipped cell is reprinted.  This function is used when the width of
-cell (ROW,COL) has changed."
+  "Same as `ses-print-cell', except if the cell's value is *skip*,
+the preceding nonskipped cell is reprinted.  This function is used
+when the width of cell (ROW,COL) has changed."
   (if (not (eq (ses-cell-value row col) '*skip*))
       (ses-print-cell row col)
     ;;Cell was skipped over - reprint previous
@@ -1235,11 +1325,9 @@ cell (ROW,COL) has changed."
 ;; The data area
 ;;----------------------------------------------------------------------------
 
-(defun ses-narrowed-p () (/= (- (point-max) (point-min)) (buffer-size)))
-
 (defun ses-widen ()
   "Turn off narrowing, to be reenabled at end of command loop."
-  (if (ses-narrowed-p)
+  (if (buffer-narrowed-p)
       (setq ses--deferred-narrow t))
   (widen))
 
@@ -1261,6 +1349,24 @@ ses--default-printer, ses--numrows, or ses--numcols."
       (goto-char ses--params-marker)
       (forward-line def))))
 
+(defun ses-file-format-extend-paramter-list (new-file-format)
+  "Extend the global parameters list when file format is updated
+from 2 to 3. This happens when local printer function are added
+to a sheet that was created with SES version 2. This is not
+undoable. Return nil when there was no change, and non nil otherwise."  
+  (save-excursion
+    (cond
+     ((and (= ses--file-format 2) (= 3 new-file-format))
+      (ses-set-parameter 'ses--file-format 3)
+      (message "Upgrading from SES-2 to SES-3 file format")
+      (ses-widen)
+      (goto-char ses--params-marker)
+      (forward-line   (plist-get ses-paramlines-plist 'ses--numlocprn ))
+      (insert (format (plist-get ses-paramfmt-plist 'ses--numlocprn)
+                      ses--numlocprn)
+             ?\n)
+      t) )))
+
 (defun ses-set-parameter (def value &optional elem)
   "Set parameter DEF to VALUE (with undo) and write the value to the data area.
 See `ses-goto-data' for meaning of DEF.  Newlines in the data are escaped.
@@ -1270,13 +1376,7 @@ If ELEM is specified, it is the array subscript within DEF to be set to VALUE."
     ;; in case one of them is being changed.
     (ses-goto-data def)
     (let ((inhibit-read-only t)
-         (fmt (plist-get '(ses--col-widths      "(ses-column-widths %S)"
-                           ses--col-printers    "(ses-column-printers %S)"
-                           ses--default-printer "(ses-default-printer %S)"
-                           ses--header-row      "(ses-header-row %S)"
-                           ses--file-format     " %S ;SES file-format"
-                           ses--numrows         " %S ;numrows"
-                           ses--numcols         " %S ;numcols")
+         (fmt (plist-get ses-paramfmt-plist
                          def))
          oldval)
       (if elem
@@ -1315,24 +1415,17 @@ Newlines in the data are escaped."
              (setq formula (cadr formula)))
          (if (eq (car-safe printer) 'ses-safe-printer)
              (setq printer (cadr printer)))
-         ;; This is noticably faster than (format "%S %S %S %S %S")
-         (setq text    (concat "(ses-cell "
-                               (symbol-name sym)
-                               " "
-                               (prin1-to-string (symbol-value sym))
-                               " "
-                               (prin1-to-string formula)
-                               " "
-                               (prin1-to-string printer)
-                               " "
-                               (if (atom (ses-cell-references cell))
-                                   "nil"
-                                 (concat "("
-                                         (mapconcat 'symbol-name
-                                                    (ses-cell-references cell)
-                                                    " ")
-                                         ")"))
-                               ")"))
+         (setq text (prin1-to-string
+                      ;; We could shorten it to (ses-cell SYM VAL) when
+                      ;; the other parameters are nil, but in practice most
+                      ;; cells have non-nil `references', so it's
+                      ;; rather pointless.
+                      `(ses-cell ,sym
+                                 ,(symbol-value sym)
+                                 ,(unless (equal formula (symbol-value sym))
+                                    formula)
+                                 ,printer
+                                 ,(ses-cell-references cell))))
          (ses-goto-data row col)
          (delete-region (point) (line-end-position))
          (insert text)))
@@ -1344,13 +1437,13 @@ Newlines in the data are escaped."
 ;;----------------------------------------------------------------------------
 
 (defun ses-formula-references (formula &optional result-so-far)
-  "Produce a list of symbols for cells that this formula's value
+  "Produce a list of symbols for cells that this FORMULA's value
 refers to.  For recursive calls, RESULT-SO-FAR is the list being
 constructed, or t to get a wrong-type-argument error when the
 first reference is found."
   (if (ses-sym-rowcol formula)
-      ;;Entire formula is one symbol
-      (add-to-list 'result-so-far formula)
+      ;; Entire formula is one symbol.
+      (cl-pushnew formula result-so-far :test #'equal)
     (if (consp formula)
        (cond
         ((eq (car formula) 'ses-range)
@@ -1358,7 +1451,7 @@ first reference is found."
                   (cdr (funcall 'macroexpand
                                 (list 'ses-range (nth 1 formula)
                                       (nth 2 formula)))))
-           (add-to-list 'result-so-far cur)))
+           (cl-pushnew cur result-so-far :test #'equal)))
         ((null (eq (car formula) 'quote))
          ;;Recursive call for subformulas
          (dolist (cur formula)
@@ -1371,7 +1464,7 @@ first reference is found."
     result-so-far)
 
 (defsubst ses-relocate-symbol (sym rowcol startrow startcol rowincr colincr)
-  "Relocate one symbol SYM, whichs corresponds to ROWCOL (a cons of ROW and
+  "Relocate one symbol SYM, which corresponds to ROWCOL (a cons of ROW and
 COL).  Cells starting at (STARTROW,STARTCOL) are being shifted
 by (ROWINCR,COLINCR)."
   (let ((row (car rowcol))
@@ -1389,8 +1482,8 @@ by (ROWINCR,COLINCR)."
 
 (defun ses-relocate-formula (formula startrow startcol rowincr colincr)
   "Produce a copy of FORMULA where all symbols that refer to cells in row
-STARTROW or above and col STARTCOL or above are altered by adding ROWINCR
-and COLINCR.  STARTROW and STARTCOL are 0-based. Example:
+STARTROW or above, and col STARTCOL or above, are altered by adding ROWINCR
+and COLINCR.  STARTROW and STARTCOL are 0-based.  Example:
        (ses-relocate-formula '(+ A1 B2 D3) 1 2 1 -1)
        => (+ A1 B2 C4)
 If ROWINCR or COLINCR is negative, references to cells being deleted are
@@ -1400,7 +1493,8 @@ removed.  Example:
 Sets `ses-relocate-return' to 'delete if cell-references were removed."
   (let (rowcol result)
     (if (or (atom formula) (eq (car formula) 'quote))
-       (if (setq rowcol (ses-sym-rowcol formula))
+       (if (and (setq rowcol (ses-sym-rowcol formula))
+                (string-match-p "\\`[A-Z]+[0-9]+\\'" (symbol-name formula)))
            (ses-relocate-symbol formula rowcol
                                 startrow startcol rowincr colincr)
          formula) ; Pass through as-is.
@@ -1501,21 +1595,22 @@ if the range was altered."
                 (funcall field (ses-sym-rowcol min))))
          ;; This range has changed size.
          (setq ses-relocate-return 'range))
-      `(ses-range ,min ,max ,@(cdddr range)))))
+      `(ses-range ,min ,max ,@(cl-cdddr range)))))
 
 (defun ses-relocate-all (minrow mincol rowincr colincr)
   "Alter all cell values, symbols, formulas, and reference-lists to relocate
 the rectangle (MINROW,MINCOL)..(NUMROWS,NUMCOLS) by adding ROWINCR and COLINCR
 to each symbol."
   (let (reform)
-    (let (mycell newval)
+    (let (mycell newval xrow)
       (dotimes-with-progress-reporter
          (row ses--numrows) "Relocating formulas..."
        (dotimes (col ses--numcols)
          (setq ses-relocate-return nil
                mycell (ses-get-cell row col)
                newval (ses-relocate-formula (ses-cell-formula mycell)
-                                            minrow mincol rowincr colincr))
+                                            minrow mincol rowincr colincr)
+               xrow  (- row rowincr))
          (ses-set-cell row col 'formula newval)
          (if (eq ses-relocate-return 'range)
              ;; This cell contains a (ses-range X Y) where a cell has been
@@ -1525,14 +1620,28 @@ to each symbol."
              ;; This cell referred to a cell that's been deleted or is no
              ;; longer part of the range.  We can't fix that now because
              ;; reference lists cells have been partially updated.
-             (add-to-list 'ses--deferred-recalc
-                          (ses-create-cell-symbol row col)))
+             (cl-pushnew (ses-create-cell-symbol row col)
+                          ses--deferred-recalc :test #'equal))
          (setq newval (ses-relocate-formula (ses-cell-references mycell)
                                             minrow mincol rowincr colincr))
          (ses-set-cell row col 'references newval)
          (and (>= row minrow) (>= col mincol)
-              (ses-set-cell row col 'symbol
-                            (ses-create-cell-symbol row col))))))
+              (let ((sym (ses-cell-symbol row col))
+                    (xcol (- col colincr)))
+                (if (and
+                     sym
+                     (>= xrow 0)
+                     (>= xcol 0)
+                     (null (eq sym
+                               (ses-create-cell-symbol xrow xcol))))
+                    ;; This is a renamed cell, do not update the cell
+                    ;; name, but just update the coordinate property.
+                    (put sym 'ses-cell (cons row col))
+                  (ses-set-cell row col 'symbol
+                                (setq sym (ses-create-cell-symbol row col)))
+                  (unless (and (boundp sym) (local-variable-p sym))
+                    (set (make-local-variable sym) nil)
+                    (put sym 'ses-cell (cons row col)))))) )))
     ;; Relocate the cell values.
     (let (oldval myrow mycol xrow xcol)
       (cond
@@ -1545,11 +1654,17 @@ to each symbol."
            (setq mycol  (+ col mincol)
                  xrow   (- myrow rowincr)
                  xcol   (- mycol colincr))
-           (if (and (< xrow ses--numrows) (< xcol ses--numcols))
-               (setq oldval (ses-cell-value xrow xcol))
-             ;; Cell is off the end of the array.
-             (setq oldval (symbol-value (ses-create-cell-symbol xrow xcol))))
-           (ses-set-cell myrow mycol 'value oldval))))
+           (let ((sym (ses-cell-symbol myrow mycol))
+                 (xsym (ses-create-cell-symbol xrow xcol)))
+             ;; Make the value relocation only when if the cell is not
+             ;; a renamed cell.  Otherwise this is not needed.
+             (and (eq sym xsym)
+                 (ses-set-cell myrow mycol 'value
+                   (if (and (< xrow ses--numrows) (< xcol ses--numcols))
+                       (ses-cell-value xrow xcol)
+                     ;;Cell is off the end of the array
+                     (symbol-value xsym))))))))
+
        ((and (wholenump rowincr) (wholenump colincr))
        ;; Insertion of rows and/or columns.  Run the loop backwards.
        (let ((disty (1- ses--numrows))
@@ -1596,97 +1711,116 @@ to each symbol."
     (insert-and-inherit "X")
     (delete-region (1- (point)) (point))))
 
-(defun ses-set-with-undo (sym newval)
-  "Like set, but undoable.  Result is t if value has changed."
-  ;; We try to avoid adding redundant entries to the undo list, but this is
-  ;; unavoidable for strings because equal ignores text properties and there's
-  ;; no easy way to get the whole property list to see if it's different!
-  (unless (and (boundp sym)
-              (equal (symbol-value sym) newval)
-              (not (stringp newval)))
-    (push (if (boundp sym)
-             `(apply ses-set-with-undo ,sym ,(symbol-value sym))
-           `(apply ses-unset-with-undo ,sym))
-         buffer-undo-list)
-    (set sym newval)
-    t))
-
-(defun ses-unset-with-undo (sym)
-  "Set SYM to be unbound.  This is undoable."
-  (when (1value (boundp sym)) ; Always bound, except after a programming error.
-    (push `(apply ses-set-with-undo ,sym ,(symbol-value sym)) buffer-undo-list)
-    (makunbound sym)))
+(defun ses-setter-with-undo (accessors newval &rest args)
+  "Set a field/variable and record it so it can be undone.
+Result is non-nil if field/variable has changed."
+  (let ((oldval (apply (car accessors) args)))
+    (unless (equal-including-properties oldval newval)
+      (push `(apply ses-setter-with-undo ,accessors ,oldval ,@args)
+            buffer-undo-list)
+      (apply (cdr accessors) newval args)
+      t)))
 
 (defun ses-aset-with-undo (array idx newval)
-  "Like aset, but undoable.  Result is t if element has changed"
-  (unless (equal (aref array idx) newval)
-    (push `(apply ses-aset-with-undo ,array ,idx
-                 ,(aref array idx)) buffer-undo-list)
-    (aset array idx newval)
-    t))
+  (ses-setter-with-undo (eval-when-compile
+                          (cons #'aref
+                                (lambda (newval array idx) (aset array idx newval))))
+                        newval array idx))
 
+(defun ses-set-with-undo (sym newval)
+  (ses-setter-with-undo
+   (eval-when-compile
+     (cons (lambda (sym) (if (boundp sym) (symbol-value sym) :ses--unbound))
+           (lambda (newval sym) (if (eq newval :ses--unbound)
+                               (makunbound sym)
+                             (set sym newval)))))
+   newval sym))
 
 ;;----------------------------------------------------------------------------
 ;; Startup for major mode
 ;;----------------------------------------------------------------------------
 
 (defun ses-load ()
-  "Parse the current buffer and sets up buffer-local variables.  Does not
-execute cell formulas or print functions."
+  "Parse the current buffer and set up buffer-local variables.
+Does not execute cell formulas or print functions."
   (widen)
   ;; Read our global parameters, which should be a 3-element list.
   (goto-char (point-max))
   (search-backward ";; Local Variables:\n" nil t)
   (backward-list 1)
   (setq ses--params-marker (point-marker))
-  (let ((params (condition-case nil (read (current-buffer)) (error nil))))
-    (or (and (= (safe-length params) 3)
+  (let* ((params (ignore-errors (read (current-buffer))))
+        (params-len (safe-length params)))
+    (or (and (>=  params-len 3)
+            (<=  params-len 4)
             (numberp (car params))
             (numberp (cadr params))
             (>= (cadr params) 0)
             (numberp (nth 2 params))
-            (> (nth 2 params) 0))
+            (> (nth 2 params) 0)
+            (or (<= params-len 3)
+                (let ((numlocprn (nth 3 params))) 
+                  (and (integerp numlocprn) (>= numlocprn 0)))))
        (error "Invalid SES file"))
     (setq ses--file-format (car params)
          ses--numrows     (cadr params)
-         ses--numcols     (nth 2 params))
+         ses--numcols     (nth 2 params)
+         ses--numlocprn (or (nth 3 params) 0))
     (when (= ses--file-format 1)
       (let (buffer-undo-list) ; This is not undoable.
        (ses-goto-data 'ses--header-row)
        (insert "(ses-header-row 0)\n")
-       (ses-set-parameter 'ses--file-format 2)
-       (message "Upgrading from SES-1 file format")))
-    (or (= ses--file-format 2)
+       (ses-set-parameter 'ses--file-format 3)
+       (message "Upgrading from SES-1 to SES-2 file format")))
+    (or (<= ses--file-format 3)
        (error "This file needs a newer version of the SES library code"))
-    (ses-create-cell-variable-range 0 (1- ses--numrows) 0 (1- ses--numcols))
     ;; Initialize cell array.
     (setq ses--cells (make-vector ses--numrows nil))
     (dotimes (row ses--numrows)
-      (aset ses--cells row (make-vector ses--numcols nil))))
+      (aset ses--cells row (make-vector ses--numcols nil)))
+    ;; initialize local printer map.
+    (clrhash ses--local-printer-hashmap))
+
   ;; Skip over print area, which we assume is correct.
   (goto-char (point-min))
   (forward-line ses--numrows)
-  (or (looking-at ses-print-data-boundary)
+  (or (looking-at-p ses-print-data-boundary)
       (error "Missing marker between print and data areas"))
   (forward-char 1)
   (setq ses--data-marker (point-marker))
   (forward-char (1- (length ses-print-data-boundary)))
   ;; Initialize printer and symbol lists.
   (mapc 'ses-printer-record ses-standard-printer-functions)
-  (setq ses--symbolic-formulas nil)
+  (setq ses--symbolic-formulas                   nil)
+
+  ;; Load local printer definitions.  
+  ;; This must be loaded *BEFORE* cells and column printers because the latters
+  ;; may call them.
+  (save-excursion
+    (forward-line (* ses--numrows (1+ ses--numcols)))
+    (let ((numlocprn ses--numlocprn))
+      (setq ses--numlocprn 0)
+      (dotimes (_ numlocprn)
+       (let ((x      (read (current-buffer))))
+         (or (and (looking-at-p "\n")
+                  (eq (car-safe x) 'ses-local-printer)
+                  (apply #'ses--local-printer (cdr x)))
+             (error "local printer-def error"))
+         (setq ses--numlocprn (1+ ses--numlocprn))))))
   ;; Load cell definitions.
   (dotimes (row ses--numrows)
     (dotimes (col ses--numcols)
       (let* ((x      (read (current-buffer)))
-            (rowcol (ses-sym-rowcol (car-safe (cdr-safe x)))))
-       (or (and (looking-at "\n")
+            (sym  (car-safe (cdr-safe x))))
+       (or (and (looking-at-p "\n")
                 (eq (car-safe x) 'ses-cell)
-                (eq row (car rowcol))
-                (eq col (cdr rowcol)))
+                (ses-create-cell-variable sym row col))
            (error "Cell-def error"))
-       (eval x)))
-    (or (looking-at "\n\n")
+       (apply #'ses--cell (cdr x))))
+    (or (looking-at-p "\n\n")
        (error "Missing blank line between rows")))
+  ;; Skip local printer function declaration --- that were already loaded.
+  (forward-line (+ 2 ses--numlocprn))
   ;; Load global parameters.
   (let ((widths      (read (current-buffer)))
        (n1          (char-after (point)))
@@ -1711,8 +1845,7 @@ execute cell formulas or print functions."
     (1value (eval head-row)))
   ;; Should be back at global-params.
   (forward-char 1)
-  (or (looking-at (replace-regexp-in-string "1" "[0-9]+"
-                                           ses-initial-global-parameters))
+  (or (looking-at-p ses-initial-global-parameters-re)
       (error "Problem with column-defs or global-params"))
   ;; Check for overall newline count in definitions area.
   (forward-line 3)
@@ -1793,13 +1926,39 @@ Delete overlays, remove special text properties."
 ;;;###autoload
 (defun ses-mode ()
   "Major mode for Simple Emacs Spreadsheet.
-See \"ses-example.ses\" (in `data-directory') for more info.
 
-Key definitions:
+When you invoke SES in a new buffer, it is divided into cells
+that you can enter data into.  You can navigate the cells with
+the arrow keys and add more cells with the tab key.  The contents
+of these cells can be numbers, text, or Lisp expressions. (To
+enter text, enclose it in double quotes.)
+
+In an expression, you can use cell coordinates to refer to the
+contents of another cell.  For example, you can sum a range of
+cells with `(+ A1 A2 A3)'.  There are specialized functions like
+`ses+' (addition for ranges with empty cells), `ses-average' (for
+performing calculations on cells), and `ses-range' and `ses-select'
+\(for extracting ranges of cells).
+
+Each cell also has a print function that controls how it is
+displayed.
+
+Each SES buffer is divided into a print area and a data area.
+Normally, you can simply use SES to look at and manipulate the print
+area, and let SES manage the data area outside the visible region.
+
+See \"ses-example.ses\" (in `data-directory') for an example
+spreadsheet, and the Info node `(ses)Top.'
+
+In the following, note the separate keymaps for cell editing mode
+and print mode specifications.  Key definitions:
+
 \\{ses-mode-map}
-These key definitions are active only in the print area (the visible part):
+These key definitions are active only in the print area (the visible
+part):
 \\{ses-mode-print-map}
-These are active only in the minibuffer, when entering or editing a formula:
+These are active only in the minibuffer, when entering or editing a
+formula:
 \\{ses-mode-edit-map}"
   (interactive)
   (unless (and (boundp 'ses--deferred-narrow)
@@ -1818,7 +1977,8 @@ These are active only in the minibuffer, when entering or editing a formula:
          ;; calculation).
          indent-tabs-mode       nil)
     (1value (add-hook 'change-major-mode-hook 'ses-cleanup nil t))
-    (1value (add-hook 'before-revert-hook 'ses-cleanup nil t))
+    ;; This makes revert impossible if the buffer is read-only.
+    ;; (1value (add-hook 'before-revert-hook 'ses-cleanup nil t))
     (setq header-line-format   '(:eval (progn
                                         (when (/= (window-hscroll)
                                                   ses--header-hscroll)
@@ -1894,7 +2054,7 @@ narrows the buffer now."
          ;; do the narrowing.
          (narrow-to-region (point-min) ses--data-marker)
          (setq ses--deferred-narrow nil))
-       ;; Update the modeline.
+       ;; Update the mode line.
        (let ((oldcell ses--curcell))
          (ses-set-curcell)
          (unless (eq ses--curcell oldcell)
@@ -1983,9 +2143,8 @@ Based on the current set of columns and `window-hscroll' position."
 
 (defun ses-jump-safe (cell)
   "Like `ses-jump', but no error if invalid cell."
-  (condition-case nil
-      (ses-jump cell)
-    (error)))
+  (ignore-errors
+    (ses-jump cell)))
 
 (defun ses-reprint-all (&optional nonarrow)
   "Recreate the display area.  Calls all printer functions.  Narrows to
@@ -2003,7 +2162,7 @@ print area if NONARROW is nil."
     (delete-region (point-min) (point))
     ;; Insert all blank lines before printing anything, so ses-print-cell can
     ;; find the data area when inserting or deleting *skip* values for cells.
-    (dotimes (row ses--numrows)
+    (dotimes (_ ses--numrows)
       (insert-and-inherit ses--blank-line))
     (dotimes-with-progress-reporter (row ses--numrows) "Reprinting..."
       (if (eq (ses-cell-value row 0) '*skip*)
@@ -2035,9 +2194,10 @@ to are recalculated first."
        (when
          (setq cur-rowcol (ses-sym-rowcol ses--curcell)
                sig (progn
-                     (ses-cell-property-set :ses-Dijkstra-attempt
-                                            (cons ses--Dijkstra-attempt-nb 0)
-                                            (car cur-rowcol) (cdr cur-rowcol) )
+                     (setf (ses-cell-property :ses-Dijkstra-attempt
+                                               (car cur-rowcol)
+                                               (cdr cur-rowcol))
+                            (cons ses--Dijkstra-attempt-nb 0))
                      (ses-calculate-cell (car cur-rowcol) (cdr cur-rowcol) t)))
          (nconc sig (list (ses-cell-symbol (car cur-rowcol)
                                            (cdr cur-rowcol)))))
@@ -2050,14 +2210,14 @@ to are recalculated first."
              ;; The t causes an error if the cell has references.  If no
              ;; references, the t will be the result value.
              (1value (ses-formula-references (ses-cell-formula row col) t))
-             (ses-cell-property-set :ses-Dijkstra-attempt
-                                    (cons ses--Dijkstra-attempt-nb 0)
-                                    row col)
+             (setf (ses-cell-property :ses-Dijkstra-attempt row col)
+                    (cons ses--Dijkstra-attempt-nb 0))
              (when (setq sig (ses-calculate-cell row col t))
                (nconc sig (list (ses-cell-symbol row col)))))
          (wrong-type-argument
           ;; The formula contains a reference.
-          (add-to-list 'ses--deferred-recalc (ses-cell-symbol row col))))))
+          (cl-pushnew (ses-cell-symbol row col) ses--deferred-recalc
+                       :test #'equal)))))
     ;; Do the update now, so we can force recalculation.
     (let ((x ses--deferred-recalc))
       (setq ses--deferred-recalc nil)
@@ -2082,8 +2242,7 @@ to are recalculated first."
     (ses-jump-safe startcell)))
 
 (defun ses-truncate-cell ()
-  "Reprint current cell, but without spillover into any following blank
-cells."
+  "Reprint current cell, but without spillover into any following blank cells."
   (interactive "*")
   (ses-check-curcell)
   (let* ((rowcol (ses-sym-rowcol ses--curcell))
@@ -2133,7 +2292,7 @@ cells."
       (insert ses-initial-file-trailer)
       (goto-char (point-min)))
     ;; Create a blank display area.
-    (dotimes (row ses--numrows)
+    (dotimes (_ ses--numrows)
       (insert ses--blank-line))
     (insert ses-print-data-boundary)
     (backward-char (1- (length ses-print-data-boundary)))
@@ -2203,16 +2362,23 @@ cell formula was unsafe and user declined confirmation."
      (barf-if-buffer-read-only)
      (list (car rowcol)
           (cdr rowcol)
-           (read-from-minibuffer
-            (format "Cell %s: " ses--curcell)
-            (cons (if (equal initial "\"") "\"\""
-                    (if (equal initial "(") "()" initial)) 2)
-            ses-mode-edit-map
-            t                         ; Convert to Lisp object.
-            'ses-read-cell-history
-            (prin1-to-string (if (eq (car-safe curval) 'ses-safe-formula)
-                                (cadr curval)
-                              curval))))))
+           (if (equal initial "\"")
+               (progn
+                 (if (not (stringp curval)) (setq curval nil))
+                 (read-string (if curval
+                                  (format "String Cell %s (default %s): "
+                                          ses--curcell curval)
+                                (format "String Cell %s: " ses--curcell))
+                              nil 'ses-read-string-history curval))
+             (read-from-minibuffer
+              (format "Cell %s: " ses--curcell)
+              (cons (if (equal initial "(") "()" initial) 2)
+              ses-mode-edit-map
+              t                         ; Convert to Lisp object.
+              'ses-read-cell-history
+              (prin1-to-string (if (eq (car-safe curval) 'ses-safe-formula)
+                                   (cadr curval)
+                                 curval)))))))
   (when (ses-edit-cell row col newval)
     (ses-command-hook) ; Update cell widths before movement.
     (dolist (x ses-after-entry-functions)
@@ -2245,7 +2411,7 @@ With prefix, deletes several cells."
       (1value (ses-clear-cell-backward (- count)))
     (ses-check-curcell)
     (ses-begin-change)
-    (dotimes (x count)
+    (dotimes (_ count)
       (ses-set-curcell)
       (let ((rowcol (ses-sym-rowcol ses--curcell)))
        (or rowcol (signal 'end-of-buffer nil))
@@ -2260,7 +2426,7 @@ cells."
       (1value (ses-clear-cell-forward (- count)))
     (ses-check-curcell 'end)
     (ses-begin-change)
-    (dotimes (x count)
+    (dotimes (_ count)
       (backward-char 1) ; Will signal 'beginning-of-buffer if appropriate.
       (ses-set-curcell)
       (let ((rowcol (ses-sym-rowcol ses--curcell)))
@@ -2272,12 +2438,14 @@ cells."
 ;;----------------------------------------------------------------------------
 
 (defun ses-read-printer (prompt default)
-  "Common code for `ses-read-cell-printer', `ses-read-column-printer', and `ses-read-default-printer'.
-PROMPT should end with \": \".  Result is t if operation was cancelled."
+  "Common code for functions `ses-read-cell-printer', `ses-read-column-printer',
+`ses-read-default-printer' and `ses-define-local-printer'.
+PROMPT should end with \": \".  Result is t if operation was
+canceled."
   (barf-if-buffer-read-only)
   (if (eq default t)
       (setq default "")
-    (setq prompt (format "%s [currently %S]: "
+    (setq prompt (format "%s (default %S): "
                         (substring prompt 0 -2)
                         default)))
   (let ((new (read-from-minibuffer prompt
@@ -2293,6 +2461,7 @@ PROMPT should end with \": \".  Result is t if operation was cancelled."
       (or (not new)
          (stringp new)
          (stringp (car-safe new))
+         (and (symbolp new) (gethash new ses--local-printer-hashmap))
          (ses-warn-unsafe new 'unsafep-function)
          (setq new t)))
     new))
@@ -2307,21 +2476,20 @@ one argument, or a symbol that names a function of one argument.  In the
 latter two cases, the function's result should be either a string (will be
 right-justified) or a list of one string (will be left-justified)."
   (interactive
-   (let ((default t)
-        x)
+   (let ((default t))
      (ses-check-curcell 'range)
      ;;Default is none if not all cells in range have same printer
      (catch 'ses-read-cell-printer
        (ses-dorange ses--curcell
-        (setq x (ses-cell-printer row col))
-        (if (eq (car-safe x) 'ses-safe-printer)
-            (setq x (cadr x)))
-        (if (eq default t)
-            (setq default x)
-          (unless (equal default x)
-            ;;Range contains differing printer functions
-            (setq default t)
-            (throw 'ses-read-cell-printer t)))))
+        (let ((x (ses-cell-printer row col)))
+           (if (eq (car-safe x) 'ses-safe-printer)
+               (setq x (cadr x)))
+           (if (eq default t)
+               (setq default x)
+             (unless (equal default x)
+               ;;Range contains differing printer functions
+               (setq default t)
+               (throw 'ses-read-cell-printer t))))))
      (list (ses-read-printer (format "Cell %S printer: " ses--curcell)
                             default))))
   (unless (eq newval t)
@@ -2331,8 +2499,8 @@ right-justified) or a list of one string (will be left-justified)."
       (ses-print-cell row col))))
 
 (defun ses-read-column-printer (col newval)
-  "Set the printer function for the current column.  See
-`ses-read-cell-printer' for input forms."
+  "Set the printer function for the current column.
+See `ses-read-cell-printer' for input forms."
   (interactive
    (let ((col (cdr (ses-sym-rowcol ses--curcell))))
      (ses-check-curcell)
@@ -2348,8 +2516,8 @@ right-justified) or a list of one string (will be left-justified)."
        (ses-print-cell row col)))))
 
 (defun ses-read-default-printer (newval)
-  "Set the default printer function for cells that have no other.  See
-`ses-read-cell-printer' for input forms."
+  "Set the default printer function for cells that have no other.
+See `ses-read-cell-printer' for input forms."
   (interactive
    (list (ses-read-printer "Default printer: " ses--default-printer)))
   (unless (eq newval t)
@@ -2363,8 +2531,8 @@ right-justified) or a list of one string (will be left-justified)."
 ;;----------------------------------------------------------------------------
 
 (defun ses-insert-row (count)
-  "Insert a new row before the current one.  With prefix, insert COUNT rows
-before current one."
+  "Insert a new row before the current one.
+With prefix, insert COUNT rows before current one."
   (interactive "*p")
   (ses-check-curcell 'end)
   (or (> count 0) (signal 'args-out-of-range nil))
@@ -2416,8 +2584,8 @@ before current one."
     (ses-goto-print (1- ses--numrows) 0)))
 
 (defun ses-delete-row (count)
-  "Delete the current row.  With prefix, Deletes COUNT rows starting from the
-current one."
+  "Delete the current row.
+With prefix, deletes COUNT rows starting from the current one."
   (interactive "*p")
   (ses-check-curcell)
   (or (> count 0) (signal 'args-out-of-range nil))
@@ -2509,8 +2677,8 @@ If COL is specified, the new column(s) get the specified WIDTH and PRINTER
   (ses-jump-safe ses--curcell))
 
 (defun ses-delete-column (count)
-  "Delete the current column.  With prefix, Deletes COUNT columns starting
-from the current one."
+  "Delete the current column.
+With prefix, deletes COUNT columns starting from the current one."
   (interactive "*p")
   (ses-check-curcell)
   (or (> count 0) (signal 'args-out-of-range nil))
@@ -2584,7 +2752,7 @@ inserts a new row if at bottom of print area.  Repeat COUNT times."
     (forward-char)))
 
 (defun ses-append-row-jump-first-column ()
-  "Insert a new row after current one and jumps to its first column."
+  "Insert a new row after current one and jump to its first column."
   (interactive "*")
   (ses-check-curcell)
   (ses-begin-change)
@@ -2600,7 +2768,7 @@ inserts a new row if at bottom of print area.  Repeat COUNT times."
      (list col
           (if current-prefix-arg
               (prefix-numeric-value current-prefix-arg)
-            (read-from-minibuffer (format "Column %s width [currently %d]: "
+            (read-from-minibuffer (format "Column %s width (default %d): "
                                           (ses-column-letter col)
                                           (ses-col-width col))
                                   nil  ; No initial contents.
@@ -2625,8 +2793,9 @@ inserts a new row if at bottom of print area.  Repeat COUNT times."
 ;; Cut and paste, import and export
 ;;----------------------------------------------------------------------------
 
-(defadvice copy-region-as-kill (around ses-copy-region-as-kill
-                               activate preactivate)
+(defun ses--advice-copy-region-as-kill (crak-fun beg end &rest args)
+  ;; FIXME: Why doesn't it make sense to copy read-only or
+  ;; intangible attributes?  They're removed upon yank!
   "It doesn't make sense to copy read-only or intangible attributes into the
 kill ring.  It probably doesn't make sense to copy keymap properties.
 We'll assume copying front-sticky properties doesn't make sense, either.
@@ -2637,14 +2806,15 @@ hard to override how mouse-1 works."
     (let ((temp beg))
       (setq beg end
            end temp)))
-  (if (not (and (eq major-mode 'ses-mode)
+  (if (not (and (derived-mode-p 'ses-mode)
                (eq (get-text-property beg 'read-only) 'ses)
                (eq (get-text-property (1- end) 'read-only) 'ses)))
-      ad-do-it ; Normal copy-region-as-kill.
+      (apply crak-fun beg end args) ; Normal copy-region-as-kill.
     (kill-new (ses-copy-region beg end))
     (if transient-mark-mode
        (setq deactivate-mark t))
     nil))
+(advice-add 'copy-region-as-kill :around #'ses--advice-copy-region-as-kill)
 
 (defun ses-copy-region (beg end)
   "Treat the region as rectangular.  Convert the intangible attributes to
@@ -2687,14 +2857,14 @@ the corresponding data cell."
   line)
 
 (defun ses-kill-override (beg end)
-  "Generic override for any commands that kill text.  We clear the killed
-cells instead of deleting them."
+  "Generic override for any commands that kill text.
+We clear the killed cells instead of deleting them."
   (interactive "r")
   (ses-check-curcell 'needrange)
   ;; For some reason, the text-read-only error is not caught by `delete-region',
   ;; so we have to use subterfuge.
   (let ((buffer-read-only t))
-    (1value (condition-case x
+    (1value (condition-case nil
                (noreturn (funcall (lookup-key (current-global-map)
                                               (this-command-keys))
                                   beg end))
@@ -2708,7 +2878,7 @@ cells instead of deleting them."
     (ses-clear-cell row col))
   (ses-jump (car ses--curcell)))
 
-(defadvice yank (around ses-yank activate preactivate)
+(defun ses--advice-yank (yank-fun &optional arg &rest args)
   "In SES mode, the yanked text is inserted as cells.
 
 If the text contains 'ses attributes (meaning it went to the kill-ring from a
@@ -2720,15 +2890,15 @@ When inserting cells, the formulas are usually relocated to keep the same
 relative references to neighboring cells.  This is best if the formulas
 generally refer to other cells within the yanked text.  You can use the C-u
 prefix to specify insertion without relocation, which is best when the
-formulas refer to cells outsite the yanked text.
+formulas refer to cells outside the yanked text.
 
 When inserting formulas, the text is treated as a string constant if it doesn't
 make sense as a sexp or would otherwise be considered a symbol.  Use 'sym to
 explicitly insert a symbol, or use the C-u prefix to treat all unmarked words
 as symbols."
-  (if (not (and (eq major-mode 'ses-mode)
+  (if (not (and (derived-mode-p 'ses-mode)
                (eq (get-text-property (point) 'keymap) 'ses-mode-print-map)))
-      ad-do-it ; Normal non-SES yank.
+      (apply yank-fun arg args) ; Normal non-SES yank.
     (ses-check-curcell 'end)
     (push-mark (point))
     (let ((text (current-kill (cond
@@ -2746,12 +2916,13 @@ as symbols."
                        arg)))
     (if (consp arg)
        (exchange-point-and-mark))))
+(advice-add 'yank :around #'ses--advice-yank)
 
 (defun ses-yank-pop (arg)
   "Replace just-yanked stretch of killed text with a different stretch.
-This command is allowed only immediately after a `yank' or a `yank-pop', when
-the region contains a stretch of reinserted previously-killed text.  We
-replace it with a different stretch of killed text.
+This command is allowed only immediately after a `yank' or a `yank-pop',
+when the region contains a stretch of reinserted previously-killed text.
+We replace it with a different stretch of killed text.
   Unlike standard `yank-pop', this function uses `undo' to delete the
 previous insertion."
   (interactive "*p")
@@ -2765,7 +2936,7 @@ previous insertion."
   (setq this-command 'yank))
 
 (defun ses-yank-cells (text arg)
-  "If the TEXT has a proper set of 'ses attributes, inserts the text as
+  "If the TEXT has a proper set of 'ses attributes, insert the text as
 cells, else return nil.  The cells are reprinted--the supplied text is
 ignored because the column widths, default printer, etc. at yank time might
 be different from those at kill-time.  ARG is a list to indicate that
@@ -2836,9 +3007,9 @@ cons of ROW and COL).  Treat plain symbols as strings unless ARG is a list."
       ;; Invalid sexp --- leave it as a string.
       (setq val (substring text from to)))
      ((and (car val) (symbolp (car val)))
-      (if (consp arg)
-         (setq val (list 'quote (car val)))  ; Keep symbol.
-       (setq val (substring text from to)))) ; Treat symbol as text.
+      (setq val (if (consp arg)
+                   (list 'quote (car val))   ; Keep symbol.
+                 (substring text from to)))) ; Treat symbol as text.
      (t
       (setq val (car val))))
     (let ((row (car rowcol))
@@ -2848,8 +3019,8 @@ cons of ROW and COL).  Treat plain symbols as strings unless ARG is a list."
       (ses-cell-set-formula row col val))))
 
 (defun ses-yank-tsf (text arg)
-  "If TEXT contains tabs and/or newlines, treats the tabs as
-column-separators and the newlines as row-separators and inserts the text as
+  "If TEXT contains tabs and/or newlines, treat the tabs as
+column-separators and the newlines as row-separators and insert the text as
 cell formulas--else return nil.  Treat plain symbols as strings unless ARG
 is a list.  Ignore a final newline."
   (if (or (not (string-match "[\t\n]" text))
@@ -2887,8 +3058,8 @@ is a list.  Ignore a final newline."
       t)))
 
 (defun ses-yank-resize (needrows needcols)
-  "If this yank will require inserting rows and/or columns, asks for
-confirmation and then inserts them.  Result is (row,col) for top left of yank
+  "If this yank will require inserting rows and/or columns, ask for
+confirmation and then insert them.  Result is (row,col) for top left of yank
 spot, or error signal if user requests cancel."
   (ses-begin-change)
   (let ((rowcol (if ses--curcell
@@ -2905,7 +3076,7 @@ spot, or error signal if user requests cancel."
                            (if rowbool (format "%d rows" needrows) "")
                            (if (and rowbool colbool) " and " "")
                            (if colbool (format "%d columns" needcols) "")))
-         (error "Cancelled"))
+         (error "Canceled"))
       (when rowbool
        (let (ses--curcell)
          (save-excursion
@@ -2918,22 +3089,22 @@ spot, or error signal if user requests cancel."
                             (ses-col-printer (1- ses--numcols)))))
     rowcol))
 
-(defun ses-export-tsv (beg end)
+(defun ses-export-tsv (_beg _end)
   "Export values from the current range, with tabs between columns and
 newlines between rows.  Result is placed in kill ring."
   (interactive "r")
   (ses-export-tab nil))
 
-(defun ses-export-tsf (beg end)
+(defun ses-export-tsf (_beg _end)
   "Export formulas from the current range, with tabs between columns and
 newlines between rows.  Result is placed in kill ring."
   (interactive "r")
   (ses-export-tab t))
 
 (defun ses-export-tab (want-formulas)
-  "Export the current range with tabs between columns and newlines between
-rows.  Result is placed in kill ring.  The export is values unless
-WANT-FORMULAS is non-nil.  Newlines and tabs in the export text are escaped."
+  "Export the current range with tabs between columns and newlines between rows.
+Result is placed in kill ring.  The export is values unless WANT-FORMULAS
+is non-nil.  Newlines and tabs in the export text are escaped."
   (ses-check-curcell 'needrange)
   (let ((print-escape-newlines t)
        result item)
@@ -2992,7 +3163,7 @@ The top row is row 1.  Selecting row 0 displays the default header row."
   (ses-reset-header-string))
 
 (defun ses-mark-row ()
-  "Marks the entirety of current row as a range."
+  "Mark the entirety of current row as a range."
   (interactive)
   (ses-check-curcell 'range)
   (let ((row (car (ses-sym-rowcol (or (car-safe ses--curcell) ses--curcell)))))
@@ -3002,7 +3173,7 @@ The top row is row 1.  Selecting row 0 displays the default header row."
     (ses-goto-print row 0)))
 
 (defun ses-mark-column ()
-  "Marks the entirety of current column as a range."
+  "Mark the entirety of current column as a range."
   (interactive)
   (ses-check-curcell 'range)
   (let ((col (cdr (ses-sym-rowcol (or (car-safe ses--curcell) ses--curcell))))
@@ -3046,13 +3217,14 @@ The top row is row 1.  Selecting row 0 displays the default header row."
        (ses-goto-print row col)))))
 
 (defun ses-renarrow-buffer ()
-  "Narrow the buffer so only the print area is visible.  Use after \\[widen]."
+  "Narrow the buffer so only the print area is visible.
+Use after \\[widen]."
   (interactive)
   (setq ses--deferred-narrow t))
 
 (defun ses-sort-column (sorter &optional reverse)
-  "Sorts the range by a specified column.  With prefix, sorts in
-REVERSE order."
+  "Sort the range by a specified column.
+With prefix, sorts in REVERSE order."
   (interactive "*sSort column: \nP")
   (ses-check-curcell 'needrange)
   (let ((min (ses-sym-rowcol (car ses--curcell)))
@@ -3103,7 +3275,7 @@ REVERSE order."
       (ses-sort-column (ses-column-letter col) reverse))))
 
 (defun ses-insert-range ()
-  "Inserts into minibuffer the list of cells currently highlighted in the
+  "Insert into minibuffer the list of cells currently highlighted in the
 spreadsheet."
   (interactive "*")
   (let (x)
@@ -3115,7 +3287,7 @@ spreadsheet."
     (insert (substring (prin1-to-string (nreverse x)) 1 -1))))
 
 (defun ses-insert-ses-range ()
-  "Inserts \"(ses-range x y)\" in the minibuffer to represent the currently
+  "Insert \"(ses-range x y)\" in the minibuffer to represent the currently
 highlighted range in the spreadsheet."
   (interactive "*")
   (let (x)
@@ -3139,31 +3311,185 @@ highlighted range in the spreadsheet."
   (mouse-set-point event)
   (ses-insert-ses-range))
 
+(defun ses-replace-name-in-formula (formula old-name new-name)
+  (let ((new-formula formula))
+    (unless (and (consp formula)
+                (eq (car-safe formula) 'quote))
+      (while formula
+       (let ((elt (car-safe formula)))
+         (cond
+          ((consp elt)
+           (setcar formula (ses-replace-name-in-formula elt old-name new-name)))
+          ((and (symbolp elt)
+                (eq (car-safe formula) old-name))
+           (setcar formula new-name))))
+       (setq formula (cdr formula))))
+    new-formula))
+
+(defun ses-rename-cell (new-name &optional cell)
+  "Rename current cell."
+  (interactive "*SEnter new name: ")
+  (or
+   (and  (local-variable-p new-name)
+        (ses-is-cell-sym-p new-name)
+        (error "Already a cell name"))
+   (and (boundp new-name)
+       (null (yes-or-no-p (format "`%S' is already bound outside this buffer, continue? "
+                                  new-name)))
+       (error "Already a bound cell name")))
+  (let* (curcell
+        (sym (if (ses-cell-p cell)
+                 (ses-cell-symbol cell)
+               (setq cell nil
+                     curcell t)
+               (ses-check-curcell)
+               ses--curcell))
+        (rowcol (ses-sym-rowcol sym))
+        (row (car rowcol))
+        (col (cdr rowcol))
+        new-rowcol old-name)
+    (setq cell (or cell (ses-get-cell row col))
+         old-name (ses-cell-symbol cell)
+         new-rowcol (ses-decode-cell-symbol (symbol-name new-name)))
+    (if new-rowcol
+       (if (equal new-rowcol rowcol)
+         (put new-name 'ses-cell rowcol)
+         (error "Not a valid name for this cell location"))
+      (setq ses--named-cell-hashmap
+            (or ses--named-cell-hashmap (make-hash-table :test 'eq)))
+      (put new-name 'ses-cell :ses-named)
+      (puthash new-name rowcol ses--named-cell-hashmap))
+    (push `(ses-rename-cell ,old-name ,cell) buffer-undo-list)
+    ;; Replace name by new name in formula of cells refering to renamed cell.
+    (dolist (ref (ses-cell-references cell))
+      (let* ((x (ses-sym-rowcol ref))
+            (xcell  (ses-get-cell (car x) (cdr x))))
+       (setf (ses-cell-formula xcell)
+              (ses-replace-name-in-formula
+               (ses-cell-formula xcell)
+               sym
+               new-name))))
+    ;; Replace name by new name in reference list of cells to which renamed
+    ;; cell refers to.
+    (dolist (ref (ses-formula-references (ses-cell-formula cell)))
+      (let* ((x (ses-sym-rowcol ref))
+            (xcell (ses-get-cell (car x) (cdr x))))
+       (setf (ses-cell-references xcell)
+              (cons new-name (delq sym
+                                   (ses-cell-references xcell))))))
+    (push new-name ses--renamed-cell-symb-list)
+    (set new-name (symbol-value sym))
+    (setf (ses-cell--symbol cell) new-name)
+    (makunbound sym)
+    (and curcell (setq ses--curcell new-name))
+    (let* ((pos (point))
+          (inhibit-read-only t)
+          (col (current-column))
+          (end (save-excursion
+                 (move-to-column (1+ col))
+                 (if (eolp)
+                     (+ pos (ses-col-width col) 1)
+                   (point)))))
+      (put-text-property pos end 'intangible new-name))
+    ;; update mode line
+    (setq mode-line-process (list " cell "
+                                 (symbol-name new-name)))
+    (force-mode-line-update)))
+
+(defun ses-refresh-local-printer (name compiled-value)
+  "Refresh printout for all cells which use printer NAME.
+NAME should be the name of a locally defined printer.
+Uses the value COMPILED-VALUE for this printer."
+  (message "Refreshing cells using printer %S" name)
+  (let (new-print)
+    (dotimes (row ses--numrows)
+      (dotimes (col ses--numcols)
+       (let ((cell-printer (ses-cell-printer row col)))
+         (when (eq cell-printer name)
+           (unless new-print
+             (setq new-print t)
+             (ses-begin-change))
+           (ses-print-cell row col)))))))
+
+(defun ses-define-local-printer (name)
+  "Define a local printer with name NAME."
+  (interactive "*SEnter printer name: ")
+  (let* ((cur-printer (gethash name ses--local-printer-hashmap))
+        (default (and (vectorp cur-printer) (ses--locprn-def cur-printer)))
+        create-printer
+        (new-def
+          (ses-read-printer (format "Enter definition of printer %S: " name)
+                            default)))
+    (cond
+     ;; cancelled operation => do nothing
+     ((eq new-def t))
+     ;; no change => do nothing
+     ((and (vectorp cur-printer) (equal new-def default)))
+     ;; re-defined printer
+     ((vectorp cur-printer)
+      (setq create-printer 0)
+      (setf (ses--locprn-def cur-printer) new-def)
+      (ses-refresh-local-printer
+       name
+       (setf (ses--locprn-compiled cur-printer)
+             (ses-local-printer-compile new-def))))
+     ;; new definition
+     (t
+      (setq create-printer 1)
+      (puthash name
+              (setq cur-printer
+                    (ses-make-local-printer-info new-def))
+              ses--local-printer-hashmap)))
+    (when create-printer
+      (let ((printer-def-text
+             (concat
+              "(ses-local-printer "
+              (symbol-name name)
+              " "
+              (prin1-to-string (ses--locprn-def cur-printer))
+              ")")))
+        (save-excursion
+          (ses-goto-data ses--numrows
+                         (ses--locprn-number cur-printer))
+          (let ((inhibit-read-only t))
+            ;; Special undo since it's outside the narrowed buffer.
+            (let (buffer-undo-list)
+              (if (= create-printer 0)
+                  (delete-region (point) (line-end-position))
+                (insert ?\n)
+                (backward-char))
+              (insert printer-def-text)
+              (when (= create-printer 1)
+                (ses-file-format-extend-paramter-list 3)
+                (ses-set-parameter 'ses--numlocprn
+                                   (+ ses--numlocprn create-printer))))))))))
+
 
 ;;----------------------------------------------------------------------------
 ;; Checking formulas for safety
 ;;----------------------------------------------------------------------------
 
 (defun ses-safe-printer (printer)
-  "Returns PRINTER if safe, or the substitute printer `ses-unsafe' otherwise."
+  "Return PRINTER if safe, or the substitute printer `ses-unsafe' otherwise."
   (if (or (stringp printer)
          (stringp (car-safe printer))
          (not printer)
+         (and (symbolp printer) (gethash printer ses--local-printer-hashmap))
          (ses-warn-unsafe printer 'unsafep-function))
       printer
     'ses-unsafe))
 
 (defun ses-safe-formula (formula)
-  "Returns FORMULA if safe, or the substitute formula *unsafe* otherwise."
+  "Return FORMULA if safe, or the substitute formula *unsafe* otherwise."
   (if (ses-warn-unsafe formula 'unsafep)
       formula
     `(ses-unsafe ',formula)))
 
 (defun ses-warn-unsafe (formula checker)
-  "Applies CHECKER to FORMULA.  If result is non-nil, asks user for
-confirmation about FORMULA, which might be unsafe.  Returns t if formula
-is safe or user allows execution anyway.  Always returns t if
-`safe-functions' is t."
+  "Apply CHECKER to FORMULA.
+If result is non-nil, asks user for confirmation about FORMULA,
+which might be unsafe.  Returns t if formula is safe or user allows
+execution anyway.  Always returns t if `safe-functions' is t."
   (if (eq safe-functions t)
       t
     (setq checker (funcall checker formula))
@@ -3178,13 +3504,13 @@ is safe or user allows execution anyway.  Always returns t if
 ;;----------------------------------------------------------------------------
 
 (defun ses--clean-! (&rest x)
-  "Clean by delq list X from any occurrence of `nil' or `*skip*'."
+  "Clean by `delq' list X from any occurrence of `nil' or `*skip*'."
   (delq nil (delq '*skip* x)))
 
 (defun ses--clean-_ (x y)
   "Clean list X  by replacing by Y any occurrence of `nil' or `*skip*'.
 
-This will change X by making setcar on its cons cells."
+This will change X by making `setcar' on its cons cells."
   (let ((ret x) ret-elt)
     (while ret
       (setq ret-elt (car ret))
@@ -3194,7 +3520,7 @@ This will change X by making setcar on its cons cells."
   x)
 
 (defmacro ses-range (from to &rest rest)
-  "Expands to a list of cell-symbols for the range going from
+  "Expand to a list of cell-symbols for the range going from
 FROM up to TO.  The range automatically expands to include any
 new row or column inserted into its middle.  The SES library code
 specifically looks for the symbol `ses-range', so don't create an
@@ -3204,11 +3530,11 @@ By passing in REST some flags one can configure the way the range
 is read and how it is formatted.
 
 In the sequel we assume that cells A1, B1, A2 B2 have respective values
-1 2 3 and 4 for examplication.
+1 2 3 and 4.
 
 Readout direction is specified by a `>v', '`>^', `<v', `<^',
-`v>', `v<', `^>', `^<' flag. For historical reasons, in absence
-of such a flag, a default direction of `^<' is assumed. This
+`v>', `v<', `^>', `^<' flag.  For historical reasons, in absence
+of such a flag, a default direction of `^<' is assumed.  This
 way `(ses-range A1 B2 ^>)' will evaluate to `(1 3 2 4)',
 while `(ses-range A1 B2 >^)' will evaluate to (3 4 1 2).
 
@@ -3221,18 +3547,18 @@ If the range is one column, then `v' can be used as a shorthand to
 A `!' flag will remove all cells whose value is nil or `*skip*'.
 
 A `_' flag will replace nil or `*skip*' by the value following
-the `_' flag. If the `_' flag is the last argument, then they are
+the `_' flag.  If the `_' flag is the last argument, then they are
 replaced by integer 0.
 
 A `*', `*1' or `*2' flag will vectorize the range in the sense of
-Calc. See info node `(Calc) Top'. Flag `*' will output either a
+Calc.  See info node `(Calc) Top'.  Flag `*' will output either a
 vector or a matrix depending on the number of rows, `*1' will
 flatten the result to a one row vector, and `*2' will make a
 matrix whatever the number of rows.
 
-Warning: interaction with Calc is expermimental and may produce
-confusing results if you are not aware of Calc data format. Use
-`math-format-value' as a printer for Calc objects."
+Warning: interaction with Calc is experimental and may produce
+confusing results if you are not aware of Calc data format.
+Use `math-format-value' as a printer for Calc objects."
   (let (result-row
        result
        (prev-row -1)
@@ -3249,19 +3575,20 @@ confusing results if you are not aware of Calc data format. Use
     (push result-row result)
     (while rest
       (let ((x (pop rest)))
-       (case x
-         ((>v) (setq transpose nil reorient-x nil reorient-y nil))
-         ((>^)(setq transpose nil reorient-x nil reorient-y t))
-         ((<^)(setq transpose nil reorient-x t reorient-y t))
-         ((<v)(setq transpose nil reorient-x t reorient-y nil))
-         ((v>)(setq transpose t reorient-x nil reorient-y t))
-         ((^>)(setq transpose t reorient-x nil reorient-y nil))
-         ((^<)(setq transpose t reorient-x t reorient-y nil))
-         ((v<)(setq transpose t reorient-x t reorient-y t))
-         ((* *2 *1) (setq vectorize x))
-         ((!) (setq clean 'ses--clean-!))
-         ((_) (setq clean `(lambda (&rest x) (ses--clean-_  x ,(if rest (pop rest) 0)))))
-         (t
+       (pcase x
+         (`>v (setq transpose nil reorient-x nil reorient-y nil))
+         (`>^ (setq transpose nil reorient-x nil reorient-y t))
+         (`<^ (setq transpose nil reorient-x t reorient-y t))
+         (`<v (setq transpose nil reorient-x t reorient-y nil))
+         (`v> (setq transpose t reorient-x nil reorient-y t))
+         (`^> (setq transpose t reorient-x nil reorient-y nil))
+         (`^< (setq transpose t reorient-x t reorient-y nil))
+         (`v< (setq transpose t reorient-x t reorient-y t))
+         ((or `* `*2 `*1) (setq vectorize x))
+         (`! (setq clean 'ses--clean-!))
+         (`_ (setq clean `(lambda (&rest x)
+                             (ses--clean-_  x ,(if rest (pop rest) 0)))))
+         (_
           (cond
                                        ; shorthands one row
            ((and (null (cddr result)) (memq x '(> <)))
@@ -3284,21 +3611,23 @@ confusing results if you are not aware of Calc data format. Use
            (setq iter (cdr iter))))
        (setq result ret)))
 
-    (flet ((vectorize-*1
-           (clean result)
-           (cons clean (cons (quote 'vec) (apply 'append result))))
-          (vectorize-*2
-           (clean result)
-           (cons clean (cons (quote 'vec) (mapcar (lambda (x)
-                                                    (cons  clean (cons (quote 'vec) x)))
-                                                  result)))))
-      (case vectorize
-       ((nil) (cons clean (apply 'append result)))
-       ((*1) (vectorize-*1 clean result))
-       ((*2) (vectorize-*2 clean result))
-       ((*) (if (cdr result)
-              (vectorize-*2 clean result)
-            (vectorize-*1 clean result)))))))
+    (cl-flet ((vectorize-*1
+               (clean result)
+               (cons clean (cons (quote 'vec) (apply 'append result))))
+              (vectorize-*2
+               (clean result)
+               (cons clean (cons (quote 'vec)
+                                 (mapcar (lambda (x)
+                                           (cons  clean (cons (quote 'vec) x)))
+                                         result)))))
+      (pcase vectorize
+       (`nil (cons clean (apply 'append result)))
+       (`*1 (vectorize-*1 clean result))
+       (`*2 (vectorize-*2 clean result))
+       (`* (funcall (if (cdr result)
+                         #'vectorize-*2
+                       #'vectorize-*1)
+                     clean result))))))
 
 (defun ses-delete-blanks (&rest args)
   "Return ARGS reversed, with the blank elements (nil and *skip*) removed."
@@ -3319,10 +3648,10 @@ are ignored.  Result is always floating-point, even if all args are integers."
   (/ (float (apply '+ list)) (length list)))
 
 (defmacro ses-select (fromrange test torange)
-  "Select cells in FROMRANGE that are `equal' to TEST.  For each match, return
-the corresponding cell from TORANGE.  The ranges are macroexpanded but not
-evaluated so they should be either (ses-range BEG END) or (list ...).  The
-TEST is evaluated."
+  "Select cells in FROMRANGE that are `equal' to TEST.
+For each match, return the corresponding cell from TORANGE.
+The ranges are macroexpanded but not evaluated so they should be
+either (ses-range BEG END) or (list ...).  The TEST is evaluated."
   (setq fromrange (cdr (macroexpand fromrange))
        torange   (cdr (macroexpand torange))
        test      (eval test))
@@ -3352,9 +3681,10 @@ TEST is evaluated."
 (defvar col)
 
 (defun ses-center (value &optional span fill)
-  "Print VALUE, centered within column.  FILL is the fill character for
-centering (default = space).  SPAN indicates how many additional rightward
-columns to include in width (default = 0)."
+  "Print VALUE, centered within column.
+FILL is the fill character for centering (default = space).
+SPAN indicates how many additional rightward columns to include
+in width (default = 0)."
   (let ((printer (or (ses-col-printer col) ses--default-printer))
        (width   (ses-col-width col))
        half)
@@ -3373,8 +3703,8 @@ columns to include in width (default = 0)."
 
 (defun ses-center-span (value &optional fill)
   "Print VALUE, centered within the span that starts in the current column
-and continues until the next nonblank column.  FILL specifies the fill
-character (default = space)."
+and continues until the next nonblank column.
+FILL specifies the fill character (default = space)."
   (let ((end (1+ col)))
     (while (and (< end ses--numcols)
                (memq (ses-cell-value row end) '(nil *skip*)))
@@ -3382,8 +3712,8 @@ character (default = space)."
     (ses-center value (- end col 1) fill)))
 
 (defun ses-dashfill (value &optional span)
-  "Print VALUE centered using dashes.  SPAN indicates how many rightward
-columns to include in width (default = 0)."
+  "Print VALUE centered using dashes.
+SPAN indicates how many rightward columns to include in width (default = 0)."
   (ses-center value span ?-))
 
 (defun ses-dashfill-span (value)
@@ -3396,8 +3726,8 @@ current column and continues until the next nonblank column."
 current column and continues until the next nonblank column."
   (ses-center-span value ?~))
 
-(defun ses-unsafe (value)
-  "Substitute for an unsafe formula or printer"
+(defun ses-unsafe (_value)
+  "Substitute for an unsafe formula or printer."
   (error "Unsafe formula or printer"))
 
 ;;All standard printers are safe, including ses-unsafe!
@@ -3406,10 +3736,9 @@ current column and continues until the next nonblank column."
 
 (defun ses-unload-function ()
   "Unload the Simple Emacs Spreadsheet."
-  (dolist (fun '(copy-region-as-kill yank))
-    (ad-remove-advice fun 'around (intern (concat "ses-" (symbol-name fun))))
-    (ad-update fun))
-  ;; continue standard unloading
+  (advice-remove 'yank #'ses--advice-yank)
+  (advice-remove 'copy-region-as-kill #'ses--advice-copy-region-as-kill)
+  ;; Continue standard unloading.
   nil)
 
 (provide 'ses)