;; ----------------------------------------------------------------------
;; latex-spread.el
;; ----------------------------------------------------------------------
;; latex-spread.el V0.9#4 -- Simple spreadsheet minor mode for LaTeX:
;; allows including formulas in LaTeX files and using emacs to update them
;;
;; M. Hermenegildo (started 23-10-95)
;; http://www.clip.dia.fi.upm.es/~herme
;; [email protected]
;; based on spread.el mode by Benjamin C. Pierce
;; with suggestions from Daniel Cabeza, Manuel Carro, Julio Marinyo,
;; and Niels L Ellegaard
;;
;; Installation instructions:
;; - Place latex-spread.el in a directory on your emacs load path
;; - Add the following lines to your .emacs file:
;; (autoload 'latex-spread-mode "latex-spread" "Simple LaTeX spreadsheet." t)
;;
;; Use: within latex-mode, AUC-TeX-mode, etc. do "M-x latex-spread-mode"
;;
;; Complete documentation appears in the header of the latex-spread-mode
;; function, at the top of this file (can be viewed easily by typing
;; C-h f and "latex-spread-mode" inside emacs once the code is
;; loaded).
;;
;; Please keep the original source information and document changes if
;; you make any modifications to this file. I would also be very
;; grateful if you send the changes back: I will try to keep an
;; updated version with any improvements that I receive. In
;; particular, it may be useful to develop a library of aggregation
;; functions and, in general, more powerful aggregation facilities.
;;
;; Now values in val need not be numbers (can be strings)
;;
;; Known bugs / future improvements:
;; - does not work correctly on Emacs-18 (and not tested on xemacs)
;; - comma format is lost if result is smaller than 1,000
;; - more flexible formats (e.g., scientific notation, in addition to
;; commas, european format) should be added
;; - handling of result being larger than the provided format could
;; be improved
;; - the imperative flavour could be eliminated if new editing commands
;; (giving new names to array variables automatically, for example)
;; were provided.
;;
;; This package is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 1, or (at your option)
;; any later version.
;; Spread-latex-mode is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
;; --------------------------------------------------------------------------
;; Set-up and keybindings
;; --------------------------------------------------------------------------
(defvar latex-spread-running-FSF19
(and (string-match "^19" emacs-version)
(not (string-match "Lucid" (emacs-version)))))
(defvar latex-spread-running-18 (string-match "^18" emacs-version))
;; Minor mode toggling vars
(defvar latex-spread-mode nil
"Non-nil if using latex-spread-mode mode as a minor mode of
some other mode.")
(make-variable-buffer-local 'latex-spread-mode)
(put 'latex-spread-mode 'permanent-local t)
;; Install mode in minor mode alist
(or (assq 'latex-spread-mode minor-mode-alist)
(setq minor-mode-alist (append minor-mode-alist
(list '(latex-spread-mode " LSp")))))
;; Key bindings
;; Made compatible with AUC-TeX bindings
(defvar latex-spread-mode-prefix-map nil)
(if latex-spread-mode-prefix-map
nil
(setq latex-spread-mode-prefix-map (make-sparse-keymap))
(define-key latex-spread-mode-prefix-map "i" 'latex-spread-insert-commands)
(define-key latex-spread-mode-prefix-map "r" 'latex-spread-recalc)
(define-key latex-spread-mode-prefix-map "1" 'latex-spread-recalc-once)
(define-key latex-spread-mode-prefix-map "v" 'latex-spread-init-vars)
(define-key latex-spread-mode-prefix-map "o" 'overwrite-mode)
)
(defvar latex-spread-mode-map nil "")
(if latex-spread-mode-map
nil
(setq latex-spread-mode-map (make-sparse-keymap))
(define-key latex-spread-mode-map "\C-c" latex-spread-mode-prefix-map))
;; Install key bindings in minor mode alist
(or (assq 'latex-spread-mode minor-mode-map-alist)
(setq minor-mode-map-alist
(cons (cons 'latex-spread-mode latex-spread-mode-map)
minor-mode-map-alist)))
;; Main
(defun latex-spread-mode (&optional arg)
"Toggle minor mode for simple spreadsheets in LaTeX files. With arg,
turn latex-spread-mode on if arg is positive, off otherwise.
Quick reference:
recalculate \\[latex-spread-recalc]
recalculate-once \\[latex-spread-recalc-once]
toggle overwrite-mode \\[overwrite-mode]
init-vars (after
introducing new variables) \\[latex-spread-init-vars]
OVERVIEW
--------
A latex-spreadsheet is an ordinary latex buffer with embedded \"cells\" of
the form
\\val{VALUE}{FORMULA}
or
\\var{VALUE}{FORMULA}{NAME}
or
\\eva{FORMULA}
where
* VALUE, the current value of the cell, is a single word (typically a
number);
* FORMULA is an arbitrary lisp expression, used to recalculate VALUE; and
* NAME, if present, is a lisp variable to which VALUE is assigned
after each recomputation.
A single recalculation step, triggered by typing
\\[latex-spread-recalc-once], consists of scanning the buffer,
recalculating each cell by replacing the current VALUE by the result
of evaluating FORMULA. A complete recalculation, triggered by typing
\\[latex-spread-recalc], iterates this process until the buffer stops
changing.
When an old value is replaced, the first character of the newly
computed value is placed in the same column as the first character of
the old. If the values are numeric, the new value is truncated to the
same number of decimal places as the old. The spacing of the
remainder of the line is preserved, except if the length of the new
value is greater that of the old one, in which case new space is
inserted.
For correct operation, a suitable definition should be provided in the
latex file for the \"val\", \"var\", and \"eva\" commands. Normally,
the idea is to print the first argument (the value) and ignore the
rest, except in \"eva\" commands, where generally nothing is
printed. The simplest example is:
\\newcommand{\\val}[2]{#1}
\\newcommand{\\var}[3]{#1}
\\newcommand{\\eva}[1]{ }
but you could also use, for example
\\newcommand{\\val}[2]{{\\bf #1}}
\\newcommand{\\var}[3]{{\\bf #1}}
\\newcommand{\\eva}[1]{ }
or similar commands.
FORMULAS
--------
The formula associated with a cell may be just a constant. This form is
useful for making names for common constants; e.g.:
\\var{10}{10}{length}
More generally, a formula may involve arbitrary arithmetic calculations
including variables whose values are set by other cells:
\\var{ 555 }{ 555 }{ breadth }
\\var{ 5550 }{ (* length breadth) }{ area }
\\var{ 29137 }{ (* area 5.25) }{ total-cost }
Values can be printed also in ``comma format,'' which is easier to
read for large numbers. This is triggered by simply including a comma
anywhere in the VALUE field: \\val{,000000000}{12345678}. Note that
enough space must be left also for the commas in the value field.
In Emacs version 19 and later (both FSF and Lucid), floating-point
numbers may also be used in formulas. If the value part of a formula
is written with a decimal point, new values will be truncated to the
same length when it is updated. As an example, here is a small LaTeX
table:
\\begin{tabular}{|r|r|r|r|}
\\hline
Nagents & Time & Speedup \\\\
\\hline
Seq. & \\var{33.0}{33.0}{ts} & \\val{1.000}{1.000 }\\\\
\\var{1}{1}{n} & \\var{33.4}{33.4}{tp} & \\val{0.988}{(/ ts tp)}\\\\
\\var{2}{2}{n} & \\var{17.5}{17.5}{tp} & \\val{1.886}{(/ ts tp)}\\\\
\\var{3}{3}{n} & \\var{11.9}{11.9}{tp} & \\val{2.773}{(/ ts tp)}\\\\
\\hline
\\end{tabular}
Variables are updated destructively, following the textual (top-down,
left-right) file order. This is not conceptually elegant but it is
very useful in practice because it allows building tables as the one
above (where the same variable name is used in each line) by simple
cutting and pasting, without having to come up with new variable names
every time. Any reference to a variable in an expression always
refers to the last assigned value.
VALUE AGGREGATION:
------------------
Sometimes it is desirable to perform operations which affect all the
values that have been given to a variable over a certain region of the
file. This is the case, for example, when computing the total for a
column in a table and in other similar aggregation functions. For this
purpose all the values that are assigned to a variable are stored in a
list which is associated with the variable as a property (the property
has the name \"history\"). This list can be accessed in any lisp
expression, which allows computing averages and more complex
functions. Also, the variable can be cleared, for example before a new
table that reuses a variable name used in a previous table, by simply
assigning the value nil to the aggregation variable. A function (gh
'var) is provided which return the history of variable (the variable
name should be quoted in the call). Also functions (ch 'var) and (sh
'var value) respectively clear the history of a variable and set the
history of variable to a given value. Here is an example of the use
of aggregation variables in the previous table:
%% This clears the value histories of tp and sp:
%% \\eva{(ch 'tp)}
%% \\eva{(ch 'sp)}
\\begin{tabular}{|r|r|r|r|}
\\hline
Nagents & Time & Speedup \\\\
\\hline
Seq. & \\var{33.0}{33.0}{ts} & \\val{1.000}{1.000 }\\\\
\\var{1}{1}{n} & \\var{33.4}{33.4}{tp} & \\val{0.988}{(/ ts tp)}\\\\
\\var{2}{2}{n} & \\var{17.5}{17.5}{tp} & \\val{1.886}{(/ ts tp)}\\\\
\\var{3}{3}{n} & \\var{11.9}{11.9}{tp} & \\val{2.773}{(/ ts tp)}\\\\
\\hline
{\\bf Sp. Avg.} & & \\val{1.882}{(/ (sumlist (gh 'sp)) (length (gh 'sp)))}\\\\
%% \\eva{(setq sum (sumlist (gh 'tp)))}
{\bf Total} & \val{62.80}{sum} & \\
\\hline
\\end{tabular}
OTHER USEFUL FUNCTIONS:
-----------------------
A possibly useful function is (date-and-time), which returns the
current date and time compressed into a single word:
\\val{ 18/10/1995-19:18 }{ (date-and-time) }
As examples of functions operating on aggregation variables a number
of other functions are provided: sumh, numh, mean, sumlist, prodlist,
etc. -- see their descriptions for more information. Others can be
included in this file or defined in the LaTeX file via an \"eva\"
construct.
Also, it is possible to use the \\eva/\\val constructs to perform other
operations. Here is an example (by Niels Langager):
\\eva{(defun greet (friend) (concat \"Hi \" friend))}
\\val{\"Hi Bob\" }{(greet \"Bob\")}
CUSTOMIZATION
-------------
Invoking latex-spread-mode calls the value of text-mode-hook and then of
latex-spread-mode-hook, if they are non-nil."
(interactive "P")
(setq latex-spread-mode
(if (null arg) (not latex-spread-mode)
(> (prefix-numeric-value arg) 0)))
(if latex-spread-mode
(progn
(latex-spread-mode-setup)
(run-hooks 'latex-spread-mode-hook))
(setq selective-display nil))
(if latex-spread-running-FSF19 (force-mode-line-update))
)
(defun latex-spread-mode-setup ()
(setq truncate-lines t)
(auto-fill-mode nil)
(latex-spread-init-vars)
)
(defun latex-spread-init-vars ()
(interactive)
(let (varchars val varname)
(latex-spread-debug "Initializing variables")
(save-excursion
(goto-char (point-min))
(while (search-forward "\\var{" (point-max) t)
(re-search-forward "[-0-9.,/\:]")
(setq val (latex-spread-number-under-cursor))
(re-search-forward "{")
(re-search-forward "{")
(re-search-forward "\\w+")
(setq varchars (buffer-substring (match-beginning 0) (match-end 0)))
(setq varname (intern varchars))
(latex-spread-debug "'%s' := '%s'" varname val)
(make-variable-buffer-local varname)
(set varname val)
(put varname 'history nil)
(latex-spread-debug "'%s' history := '%s'" varname (get varname 'history))
))))
;; --------------------------------------------------------------------------
;; Recalculation
;; --------------------------------------------------------------------------
(defvar latex-spread-recalc-limit 40
"*Maximum iterations of latex-spreadsheet recalculation")
(defun latex-spread-recalc ()
"Recalculate all computed cells in buffer, iterating until all cells'
values have stabilized or for SPREAD-RECALC-LIMIT iterations, whichever
comes first."
(interactive)
(message "Recalculating... ")
(let ((limit 0))
(while (save-excursion (latex-spread-recalc-once))
(message "Recalculating... (%s)" limit)
(sit-for 0)
(setq limit (+ limit 1))
(if (= limit latex-spread-recalc-limit)
(latex-spread-error "Recalculation looping!"))))
(message "Recalculating... done"))
(defun latex-spread-get-next-cell (cont)
(if (search-forward-regexp "[\\]\\(val\\|var\\|eva\\){" (point-max) t)
(let (after eol b e res start end contents var formula formula-start)
(latex-spread-debug "found a cell")
(backward-char 4)
(if (looking-at "eva{")
(progn
(latex-spread-debug "(an eva cell)")
(re-search-forward "{")
;; start has the point at which the formula starts
(setq formula-start (point))
(re-search-forward "}")
(backward-char 1)
;; end has the point at which the formula ends
(setq eol (point))
(setq res (read-from-string (buffer-substring formula-start eol)))
;; formula has the formula
(setq formula (car res))
(latex-spread-debug "formula: '%s'" formula)
(setq contents (latex-spread-eval formula))
(latex-spread-debug "returned value: '%s'" contents))
;; Looking at val or var
(progn
(cond
((looking-at "var{")
(latex-spread-debug "(a var cell)")
(forward-char 4)
(while (looking-at "[^-0-9.,/\:]") (forward-char 1))
;; start has the point at which the initial value starts
(setq start (point))
(while (looking-at "[-0-9.,/\:-]") (forward-char 1))
;; end has the point just after the initial value
(setq end (point))
;; now contents has the initial value
(setq contents (buffer-substring start end)))
(t
(latex-spread-debug "(a val cell)")
(forward-char 4)
;; values do not really have to be parsed:
;; a value is just an output and can be even a string
;; a value cannot contain }
(setq start (point))
(re-search-forward " *}") ;; skip trailing blanks
(backward-char 1)
(setq end (match-beginning 0))
;; (setq end (point))
;; now contents has the initial value
(setq contents (buffer-substring start end))))
(latex-spread-debug "contents: '%s'" contents)
(re-search-forward "{")
;; formula-start has the first point of the formula
(setq formula-start (point))
(re-search-forward "}")
(backward-char 1)
;; eol has the end of the formula
(setq eol (point))
(forward-char 1)
;; after has the point after the formula (to be updated later
;; if there is a variable name)
(setq after (point))
(goto-char formula-start)
;; res has (