Browse Source

Implement table formulas that apply to field ranges, fix minor issues

* lisp/org-table.el (org-table-fedit-finish): Read more general LHS of formulas.
(org-table-formula-handle-@L): New function to hanle @L references.
(org-table-current-ncol): New variable.
(org-table-line-to-dline): New function.
(org-table-get-stored-formulas): Accept range formulas as matches.
(org-table-get-specials): Compute and store the number of columns.
(org-table-get-range): New optional argument CORNERS-ONLY, to retrieve
only the region marked by the range, not the content.
(org-table-recalculate): Call `org-table-expand-lhs-ranges' to expand
range targets.  Also check for duplicate access to fields.
(org-table-expand-lhs-ranges): New funktion.
(org-table-get-remote-range): Bind `org-table-current-ncol' to protect
the caller's value.
(org-table-edit-formulas): Support highlighting of range targets.
(org-table-field-info): Handle renge formulas.

* doc/org.texi (Field and range formulas): Renamed from "Field formulas".
Document the use of range operators as targets.
(References): Document the new @L reference.
Carsten Dominik 9 years ago
parent
commit
8237c9ae6d
2 changed files with 213 additions and 76 deletions
  1. 73 44
      doc/org.texi
  2. 140 32
      lisp/org-table.el

+ 73 - 44
doc/org.texi

@@ -378,7 +378,7 @@ The spreadsheet
 * References::                  How to refer to another field or range
 * Formula syntax for Calc::     Using Calc to compute stuff
 * Formula syntax for Lisp::     Writing formulas in Emacs Lisp
-* Field formulas::              Formulas valid for a single field
+* Field and range formulas::    Formula for specific (ranges of) fields
 * Column formulas::             Formulas valid for an entire column
 * Editing and debugging formulas::  Fixing formulas
 * Updating the table::          Recomputing all dependent fields
@@ -670,6 +670,8 @@ Specific header arguments
                                 directory for code block execution
 * exports::                     Export code and/or results
 * tangle::                      Toggle tangling and specify file name
+* mkdirp::                      Toggle creation of parent directories of target
+                                files during tangling
 * comments::                    Toggle insertion of comments in tangled
                                 code files
 * no-expand::                   Turn off variable assignment and noweb
@@ -677,7 +679,7 @@ Specific header arguments
 * session::                     Preserve the state of code evaluation
 * noweb::                       Toggle expansion of noweb references
 * cache::                       Avoid re-evaluating unchanged code blocks
-* sep::                         Specify delimiter for writing external tables
+* sep::                         Delimiter for writing tabular results outside Org
 * hlines::                      Handle horizontal lines in tables
 * colnames::                    Handle column names in tables
 * rownames::                    Handle row names in tables
@@ -2243,7 +2245,7 @@ formula, moving these references by arrow keys
 * References::                  How to refer to another field or range
 * Formula syntax for Calc::     Using Calc to compute stuff
 * Formula syntax for Lisp::     Writing formulas in Emacs Lisp
-* Field formulas::              Formulas valid for a single field
+* Field and range formulas::    Formula for specific (ranges of) fields
 * Column formulas::             Formulas valid for an entire column
 * Editing and debugging formulas::  Fixing formulas
 * Updating the table::          Recomputing all dependent fields
@@ -2278,20 +2280,29 @@ Org also uses another, more general operator that looks like this:
 @end example
 
 @noindent
+and allows relative relative references, i.e. references relative to the
+row/column of the field whose value is being computed.  These relative
+references make it possible to store a formula only once and use it in many
+fields without copying and modifying it.
+
 Column references can be absolute like @samp{1}, @samp{2},...@samp{@var{N}},
 or relative to the current column like @samp{+1} or @samp{-2}.
 
-The row specification only counts data lines and ignores horizontal
-separator lines (hlines).  You can use absolute row numbers
-@samp{1}...@samp{@var{N}}, and row numbers relative to the current row like
-@samp{+3} or @samp{-1}.  Or specify the row relative to one of the
-hlines: @samp{I} refers to the first hline@footnote{Note that only
-hlines are counted that @emph{separate} table lines.  If the table
-starts with a hline above the header, it does not count.}, @samp{II} to
-the second, etc@.  @samp{-I} refers to the first such line above the
-current line, @samp{+I} to the first such line below the current line.
-You can also write @samp{III+2} which is the second data line after the
-third hline in the table.
+The row specification only counts data lines and ignores horizontal separator
+lines (hlines).  You can use absolute row numbers @samp{1}...@samp{@var{N}},
+and row numbers relative to the current row like @samp{+3} or @samp{-1}.  A
+special case is @code{@@L} which stands for the last row in the
+table@footnote{For backward compatibility you can also use special names like
+@samp{$LR5} and @samp{$LR12} to refer in a stable way to the 5th and 12th
+field in the last row of the table.  However, this syntax is deprecated, it
+should not be used for new documents.}, and you can use offsets from it like
+@code{@@L-2}.  Or specify the row relative to one of the hlines: @samp{I}
+refers to the first hline@footnote{Note that only hlines are counted that
+@emph{separate} table lines.  If the table starts with a hline above the
+header, it does not count.}, @samp{II} to the second, etc@.  @samp{-I} refers
+to the first such line above the current line, @samp{+I} to the first such
+line below the current line.  You can also write @samp{III+2} which is the
+second data line after the third hline in the table.
 
 @samp{0} refers to the current row and column.  Also, if you omit
 either the column or the row part of the reference, the current
@@ -2304,9 +2315,6 @@ Org's references with @emph{signed} numbers are floating
 references because the same reference operator can reference different
 fields depending on the field being calculated by the formula.
 
-As a special case, references like @samp{$LR5} and @samp{$LR12} can be used
-to refer in a stable way to the 5th and 12th field in the last row of the
-table.
 
 Here are a few examples:
 
@@ -2332,11 +2340,12 @@ format at least for the first field (i.e the reference must start with
 @samp{@@} in order to be interpreted correctly).  Examples:
 
 @example
-$1..$3        @r{First three fields in the current row.}
+$1..$3        @r{First three fields in the current row}
 $P..$Q        @r{Range, using column names (see under Advanced)}
-@@2$1..@@4$3    @r{6 fields between these two fields.}
-A2..C4        @r{Same as above.}
+@@2$1..@@4$3    @r{6 fields between these two fields}
+A2..C4        @r{Same as above}
 @@-1$-2..@@-1   @r{3 numbers from the column to the left, 2 up to current row}
+@@I..II        @r{Between first and second hline, short for @code{@@I..@@II}}
 @end example
 
 @noindent Range references return a vector of values that can be fed
@@ -2501,7 +2510,7 @@ Calc also contains a complete set of logical operations.  For example
 if($1<20,teen,string(""))  @r{``teen'' if age $1 less than 20, else empty}
 @end example
 
-@node Formula syntax for Lisp, Field formulas, Formula syntax for Calc, The spreadsheet
+@node Formula syntax for Lisp, Field and range formulas, Formula syntax for Calc, The spreadsheet
 @subsection Emacs Lisp forms as formulas
 @cindex Lisp forms, as table formulas
 
@@ -2520,7 +2529,7 @@ you provide the @samp{L} flag, all fields will be interpolated literally,
 without quotes.  i.e., if you want a reference to be interpreted as a string
 by the Lisp form, enclose the reference operator itself in double-quotes,
 like @code{"$3"}.  Ranges are inserted as space-separated fields, so you can
-+embed them in list or vector syntax.  Here are a few examples---note how the
+embed them in list or vector syntax.  Here are a few examples---note how the
 @samp{N} mode is used when we do computations in Lisp:
 
 @example
@@ -2532,16 +2541,18 @@ like @code{"$3"}.  Ranges are inserted as space-separated fields, so you can
   '(apply '+ '($1..$4));N
 @end example
 
-@node Field formulas, Column formulas, Formula syntax for Lisp, The spreadsheet
-@subsection Field formulas
+@node Field and range formulas, Column formulas, Formula syntax for Lisp, The spreadsheet
+@subsection Field and range formulas
 @cindex field formula
+@cindex range formula
 @cindex formula, for individual table field
+@cindex formula, for range of fields
 
-To assign a formula to a particular field, type it directly into the
-field, preceded by @samp{:=}, for example @samp{:=$1+$2}.  When you
-press @key{TAB} or @key{RET} or @kbd{C-c C-c} with the cursor still in
-the field, the formula will be stored as the formula for this field,
-evaluated, and the current field replaced with the result.
+To assign a formula to a particular field, type it directly into the field,
+preceded by @samp{:=}, for example @samp{:=vsum(@@II..III)}.  When you press
+@key{TAB} or @key{RET} or @kbd{C-c C-c} with the cursor still in the field,
+the formula will be stored as the formula for this field, evaluated, and the
+current field will be replaced with the result.
 
 @cindex #+TBLFM
 Formulas are stored in a special line starting with @samp{#+TBLFM:}
@@ -2552,9 +2563,6 @@ with the appropriate commands, @i{absolute references} (but not relative
 ones) in stored formulas are modified in order to still reference the
 same field.  Of course this is not true if you edit the table structure
 with normal editing commands---then you must fix the equations yourself.
-The left-hand side of a formula may also be a named field (@pxref{Advanced
-features}), or a last-row reference like @samp{$LR3}.
-
 Instead of typing an equation into the field, you may also use the
 following command
 
@@ -2565,17 +2573,38 @@ formula with default taken from the @samp{#+TBLFM:} line, applies
 it to the current field, and stores it.
 @end table
 
-@node Column formulas, Editing and debugging formulas, Field formulas, The spreadsheet
+The left-hand side of a formula can also be a special expression in order to
+assign the formula to a number of different fields.  There is no keyboard
+shortcut to enter such range formulas.  To add them, use the formula editor
+(@pxref{Editing and debugging formulas}) or edit the @code{#+TBLFM:} line
+directly.
+
+@table @code
+@item $2=
+Column formula, valid for the entire column.  This is so common that Org
+treats these formulas in a special way, see @ref{Column formulas}.
+@item @@3=
+Row formula, applies to all fields in the specified row.  @code{@@L=} means
+the last row.
+@item @@1$2..@@4$3=
+Range formula, applies to all fields in the given rectangular range.  This
+can also be used to assign a formula to some but not all fields in a row.
+@item $name=
+Named field, see @ref{Advanced features}.
+@end table
+
+@node Column formulas, Editing and debugging formulas, Field and range formulas, The spreadsheet
 @subsection Column formulas
 @cindex column formula
 @cindex formula, for table column
 
-Often in a table, the same formula should be used for all fields in a
-particular column.  Instead of having to copy the formula to all fields
-in that column, Org allows you to assign a single formula to an entire
-column.  If the table contains horizontal separator hlines, everything
-before the first such line is considered part of the table @emph{header}
-and will not be modified by column formulas.
+When you assign a formula to a simple column reference like @code{$3=}, the
+same formula will be used in all fields of that column, with the following
+very convenient exceptions: (i) If the table contains horizontal separator
+hlines, everything before the first such line is considered part of the table
+@emph{header} and will not be modified by column formulas.  (ii) Fields that
+already get a value from a fild/range formula will be left alone by column
+formulas.  These conditions make column formulas very easy to use.
 
 To assign a formula to a column, type it directly into any field in the
 column, preceded by an equal sign, like @samp{=$1+$2}.  When you press
@@ -2584,9 +2613,9 @@ the formula will be stored as the formula for the current column, evaluated
 and the current field replaced with the result.  If the field contains only
 @samp{=}, the previously stored formula for this column is used.  For each
 column, Org will only remember the most recently used formula.  In the
-@samp{#+TBLFM:} line, column formulas will look like @samp{$4=$1+$2}.  The left-hand
-side of a column formula cannot currently be the name of column, it
-must be the numeric column reference.
+@samp{#+TBLFM:} line, column formulas will look like @samp{$4=$1+$2}.  The
+left-hand side of a column formula can not be the name of column, it must be
+the numeric column reference.
 
 Instead of typing an equation into the field, you may also use the
 following command:
@@ -2617,7 +2646,7 @@ if possible.  If you prefer to only work with the internal format (like
 @table @kbd
 @orgcmdkkc{C-c =,C-u C-c =,org-table-eval-formula}
 Edit the formula associated with the current column/field in the
-minibuffer.  See @ref{Column formulas}, and @ref{Field formulas}.
+minibuffer.  See @ref{Column formulas}, and @ref{Field and range formulas}.
 @orgcmd{C-u C-u C-c =,org-table-eval-formula}
 Re-insert the active formula (either a
 field formula, or a column formula) into the current field, so that you
@@ -2720,7 +2749,7 @@ following commands:
 @table @kbd
 @orgcmd{C-c *,org-table-recalculate}
 Recalculate the current row by first applying the stored column formulas
-from left to right, and all field formulas in the current row.
+from left to right, and all field/range formulas in the current row.
 @c
 @kindex C-u C-c *
 @item C-u C-c *

+ 140 - 32
lisp/org-table.el

@@ -186,7 +186,7 @@ t:      accept as input and present for editing"
   :type '(choice
 	  (const :tag "Never, don't even check user input for them" nil)
 	  (const :tag "Always, both as user input, and when editing" t)
-	  (const :tag "Convert user input, don't offer during editing" 'from)))
+	  (const :tag "Convert user input, don't offer during editing" from)))
 
 (defcustom org-table-copy-increment t
   "Non-nil means increment when copying current field with \\[org-table-copy-down]."
@@ -316,6 +316,8 @@ available parameters."
   "Table begin line, non-nil only for the duration of a command.")
 (defvar org-table-current-begin-pos nil
   "Table begin position, non-nil only for the duration of a command.")
+(defvar org-table-current-ncol nil
+  "Number of columns in table, non-nil only for the duration of a command.")
 (defvar org-table-dlines nil
   "Vector of data line line numbers in the current table.")
 (defvar org-table-hlines nil
@@ -1128,13 +1130,19 @@ is always the old value."
 	   (cname (car (rassoc (int-to-string col) org-table-column-names)))
 	   (name (car (rassoc (list (org-current-line) col)
 			      org-table-named-field-locations)))
-	   (eql (org-table-get-stored-formulas))
+	   (eql (org-table-expand-lhs-ranges
+		 (mapcar
+		  (lambda (e)
+		    (cons (org-table-formula-handle-@L (car e)) (cdr e)))
+		  (org-table-get-stored-formulas))))
 	   (dline (org-table-current-dline))
 	   (ref (format "@%d$%d" dline col))
 	   (ref1 (org-table-convert-refs-to-an ref))
 	   (fequation (or (assoc name eql) (assoc ref eql)))
 	   (cequation (assoc (int-to-string col) eql))
 	   (eqn (or fequation cequation)))
+      (if (and eqn (get-text-property 0 :orig-eqn (car eqn)))
+	  (setq eqn (get-text-property 0 :orig-eqn (car eqn))))
       (goto-char pos)
       (condition-case nil
 	  (org-table-show-reference 'local)
@@ -1246,6 +1254,28 @@ However, when FORCE is non-nil, create new columns if necessary."
 	(error
 	 "Please position cursor in a data line for column operations")))))
 
+(defun org-table-line-to-dline (line &optional above)
+  "Turn a buffer line number into a data line number.
+If there is no data line in this line, return nil.
+If there is no matchin dline (most likely te refrence was a hline), the
+first dline below it is used.  When ABOVE is non-nil, the one above is used."
+  (catch 'exit
+    (let ((ll (length org-table-dlines))
+	  i)
+      (if above
+	  (progn
+	    (setq i (1- ll))
+	    (while (> i 0)
+	      (if (<= (aref org-table-dlines i) line)
+		  (throw 'exit i))
+	      (setq i (1- i))))
+	(setq i 1)
+	(while (< i ll)
+	  (if (>= (aref org-table-dlines i) line)
+	      (throw 'exit i))
+	(setq i (1+ i)))))
+      nil))
+
 (defun org-table-delete-column ()
   "Delete a column from the table."
   (interactive)
@@ -1966,7 +1996,7 @@ When NAMED is non-nil, look for a named equation."
       (when (looking-at "\\([ \t]*\n\\)*[ \t]*#\\+TBLFM: *\\(.*\\)")
 	(setq strings (org-split-string (match-string 2) " *:: *"))
 	(while (setq string (pop strings))
-	  (when (string-match "\\`\\(@[0-9]+\\$[0-9]+\\|\\$\\([a-zA-Z0-9]+\\)\\) *= *\\(.*[^ \t]\\)" string)
+	  (when (string-match "\\`\\(@[-+LI0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\)\\) *= *\\(.*[^ \t]\\)" string)
 	    (setq scol (if (match-end 2)
 			   (match-string 2 string)
 			 (match-string 1 string))
@@ -2022,7 +2052,8 @@ For all numbers larger than LIMIT, shift them by DELTA."
 	    org-table-named-field-locations nil
 	    org-table-current-begin-line nil
 	    org-table-current-begin-pos nil
-	    org-table-current-line-types nil)
+	    org-table-current-line-types nil
+	    org-table-current-ncol 0)
       (goto-char beg)
       (when (re-search-forward "^[ \t]*| *! *\\(|.*\\)" end t)
 	(setq names (org-split-string (match-string 1) " *| *")
@@ -2078,6 +2109,7 @@ For all numbers larger than LIMIT, shift them by DELTA."
 		      "[ \t]*|[ \t]*"))
 	     (nfields (length fields))
 	     al al2)
+	(setq org-table-current-ncol nfields)
 	(loop for i from 1 to nfields do
 	      (push (list (format "LR%d" i) l i) al)
 	      (push (cons (format "LR%d" i) (nth (1- i) fields)) al2))
@@ -2086,7 +2118,6 @@ For all numbers larger than LIMIT, shift them by DELTA."
 	(setq org-table-local-parameters
 	      (append org-table-local-parameters al2))))))
 
-
 (defun org-table-maybe-eval-formula ()
   "Check if the current field starts with \"=\" or \":=\".
 If yes, store the formula and apply it."
@@ -2415,11 +2446,16 @@ $1->    %s\n" orig formula form0 form))
 		       (progn (skip-chars-forward "^|") (point))
 		       prop value)))
 
-(defun org-table-get-range (desc &optional tbeg col highlight)
+(defun org-table-get-range (desc &optional tbeg col highlight corners-only)
   "Get a calc vector from a column, according to descriptor DESC.
 Optional arguments TBEG and COL can give the beginning of the table and
 the current column, to avoid unnecessary parsing.
-HIGHLIGHT means just highlight the range."
+
+HIGHLIGHT means just highlight the range.
+
+When CORNERS-ONLY is set, only return the corners of the range as
+a list (line1 column1 line2 column2) where line1 and line2 are line numbers
+in the buffer and column1 and column2 are table column numbers."
   (if (not (equal (string-to-char desc) ?@))
       (setq desc (concat "@" desc)))
   (save-excursion
@@ -2448,7 +2484,8 @@ HIGHLIGHT means just highlight the range."
       (if (not r2) (setq r2 thisline))
       (if (not c1) (setq c1 col))
       (if (not c2) (setq c2 col))
-      (if (or (not rangep) (and (= r1 r2) (= c1 c2)))
+      (if (and (not corners-only)
+	       (or (not rangep) (and (= r1 r2) (= c1 c2))))
 	  ;; just one field
 	  (progn
 	    (org-goto-line r1)
@@ -2460,22 +2497,26 @@ HIGHLIGHT means just highlight the range."
 	;; First sort the numbers to get a regular ractangle
 	(if (< r2 r1) (setq tmp r1 r1 r2 r2 tmp))
 	(if (< c2 c1) (setq tmp c1 c1 c2 c2 tmp))
-	(org-goto-line r1)
-	(while (not (looking-at org-table-dataline-regexp))
-	  (beginning-of-line 2))
-	(org-table-goto-column c1)
-	(setq beg (point))
-	(org-goto-line r2)
-	(while (not (looking-at org-table-dataline-regexp))
-	  (beginning-of-line 0))
-	(org-table-goto-column c2)
-	(setq end (point))
-	(if highlight
-	    (org-table-highlight-rectangle
-	     beg (progn (skip-chars-forward "^|\n") (point))))
-	;; return string representation of calc vector
-	(mapcar 'org-trim
-		(apply 'append (org-table-copy-region beg end)))))))
+	(if corners-only
+	    ;; Only return the corners of the range
+	    (list r1 c1 r2 c2)
+	  ;; Copy the range values into a list
+	  (org-goto-line r1)
+	  (while (not (looking-at org-table-dataline-regexp))
+	    (beginning-of-line 2))
+	  (org-table-goto-column c1)
+	  (setq beg (point))
+	  (org-goto-line r2)
+	  (while (not (looking-at org-table-dataline-regexp))
+	    (beginning-of-line 0))
+	  (org-table-goto-column c2)
+	  (setq end (point))
+	  (if highlight
+	      (org-table-highlight-rectangle
+	       beg (progn (skip-chars-forward "^|\n") (point))))
+	  ;; return string representation of calc vector
+	  (mapcar 'org-trim
+		  (apply 'append (org-table-copy-region beg end))))))))
 
 (defun org-table-get-descriptor-line (desc &optional cline bline table)
   "Analyze descriptor DESC and retrieve the corresponding line number.
@@ -2595,12 +2636,15 @@ known that the table will be realigned a little later anyway."
 	   (line-re org-table-dataline-regexp)
 	   (thisline (org-current-line))
 	   (thiscol (org-table-current-column))
-	   beg end entry eqlnum eqlname eqlname1 eql (cnt 0) eq a name)
+	   seen-fields
+	   beg end entry eqlnum eqlname eqlname1 eql (cnt 0) eq a name name1)
       ;; Insert constants in all formulas
       (setq eqlist
 	    (mapcar (lambda (x)
-		      (setcdr x (org-table-formula-substitute-names (cdr x)))
-		      x)
+		      (cons
+		       (org-table-formula-handle-@L (car x))
+		       (org-table-formula-substitute-names
+			(org-table-formula-handle-@L (cdr x)))))
 		    eqlist))
       ;; Split the equation list
       (while (setq eq (pop eqlist))
@@ -2608,6 +2652,10 @@ known that the table will be realigned a little later anyway."
 	    (push eq eqlnum)
 	  (push eq eqlname)))
       (setq eqlnum (nreverse eqlnum) eqlname (nreverse eqlname))
+      ;; Expand ranges in lhs of formulas
+      (setq eqlname (org-table-expand-lhs-ranges eqlname))
+      
+      ;; Get the correct line range to process
       (if all
 	  (progn
 	    (setq end (move-marker (make-marker) (1+ (org-table-end))))
@@ -2626,11 +2674,19 @@ known that the table will be realigned a little later anyway."
       (goto-char beg)
       (and all (message "Re-applying formulas to full table..."))
 
-      ;; First find the named fields, and mark them untouchable
+      ;; First find the named fields, and mark them untouchable.
+      ;; Also check if several field/range formulas try to set the same field.
       (remove-text-properties beg end '(org-untouchable t))
       (while (setq eq (pop eqlname))
 	(setq name (car eq)
 	      a (assoc name org-table-named-field-locations))
+	(setq name1 name)
+	(if a (setq name1 (format "@%d$%d" (org-table-line-to-dline (nth 1 a))
+				  (nth 2 a))))
+	(when (member name1 seen-fields)
+	      (error "Several field/range formulas try to set %s" name1))
+	(push name1 seen-fields)
+	  
 	(and (not a)
 	     (string-match "@\\([0-9]+\\)\\$\\([0-9]+\\)" name)
 	     (setq a (list name
@@ -2646,7 +2702,7 @@ known that the table will be realigned a little later anyway."
 	  (org-table-goto-column (nth 2 a))
 	  (push (append a (list (cdr eq))) eqlname1)
 	  (org-table-put-field-property :org-untouchable t)))
-
+      
       ;; Now evaluate the column formulas, but skip fields covered by
       ;; field formulas
       (goto-char beg)
@@ -2735,6 +2791,50 @@ known that the table will be realigned a little later anyway."
 	     (setq checksum c1)))
 	 (error "No convergence after %d iterations" imax))))))
 
+(defun org-table-expand-lhs-ranges (equations)
+  "Expand list of formulas.
+If some of the RHS in the formulas are ranges or a row reference, expand
+them to individual field equations for each field."
+  (let (e res lhs rhs range r1 r2 c1 c2)
+    (while (setq e (pop equations))
+      (setq lhs (car e) rhs (cdr e))
+      (cond
+       ((string-match "^@-?[-+I0-9]+\\$-?[0-9]+$" lhs)
+	;; This just refers to one fixed field
+	(push e res))
+       ((string-match "^[a-zA-Z][a-zA-Z0-9]*$" lhs)
+	;; This just refers to one fixed named field
+	(push e res))
+       ((string-match "^@[0-9]+$" lhs)
+	(loop for ic from 1 to org-table-current-ncol do
+	      (push (cons (format "%s$%d" lhs ic) rhs) res)
+	      (put-text-property 0 (length (caar res))
+				 :orig-eqn e (caar res))))
+       (t
+	(setq range (org-table-get-range lhs org-table-current-begin-pos
+					 1 nil 'corners))
+	(setq r1 (nth 0 range) c1 (nth 1 range)
+	      r2 (nth 2 range) c2 (nth 3 range))
+	(setq r1 (org-table-line-to-dline r1))
+	(setq r2 (org-table-line-to-dline r2 'above))
+	(loop for ir from r1 to r2 do
+	      (loop for ic from c1 to c2 do
+		    (push (cons (format "@%d$%d" ir ic) rhs) res)
+		    (put-text-property 0 (length (caar res))
+				       :orig-eqn e (caar res)))))))
+    (nreverse res)))
+
+(defun org-table-formula-handle-@L (s)
+  "Replace @L with the last row data row of the table."
+  (while (string-match "@L\\(-[0-9]+\\)?" s)
+    (setq s (replace-match
+	     (format "@%d" (+ (length org-table-dlines) -1
+			      (if (match-end 1)
+				  (string-to-number (match-string 1 s))
+				0)))
+	     t t s)))
+  s)
+
 (defun org-table-formula-substitute-names (f)
   "Replace $const with values in string F."
   (let ((start 0) a (f1 f) (pp (/= (string-to-char f) ?')))
@@ -2837,7 +2937,7 @@ Parameters get priority."
 	(wc (current-window-configuration))
 	(sel-win (selected-window))
 	(titles '((column . "# Column Formulas\n")
-		  (field . "# Field Formulas\n")
+		  (field . "# Field and Range Formulas\n")
 		  (named . "# Named Field Formulas\n")))
 	entry s type title)
     (org-switch-to-buffer-other-window "*Edit Formulas*")
@@ -2861,7 +2961,7 @@ Parameters get priority."
       (when (setq title (assq type titles))
 	(or (bobp) (insert "\n"))
 	(insert (org-add-props (cdr title) nil 'face font-lock-comment-face))
-	(setq titles (delq title titles)))
+	(setq titles (remove title titles)))
       (if (equal key (car entry)) (setq startline (org-current-line)))
       (setq s (concat (if (equal (string-to-char (car entry)) ?@) "" "$")
 		      (car entry) " = " (cdr entry) "\n"))
@@ -3078,7 +3178,7 @@ With prefix ARG, apply the new formulas to the table."
   (let ((pos org-pos) (sel-win org-selected-window) eql var form)
     (goto-char (point-min))
     (while (re-search-forward
-	    "^\\(@[0-9]+\\$[0-9]+\\|\\$\\([a-zA-Z0-9]+\\)\\) *= *\\(.*\\(\n[ \t]+.*$\\)*\\)"
+	    "^\\(@[-+IL0-9.$@]+\\|@?[0-9]+\\|\\$\\([a-zA-Z0-9]+\\)\\) *= *\\(.*\\(\n[ \t]+.*$\\)*\\)"
 	    nil t)
       (setq var (if (match-end 2) (match-string 2) (match-string 1))
 	    form (match-string 3))
@@ -3167,6 +3267,12 @@ With prefix ARG, apply the new formulas to the table."
 	  var name e what match dest)
       (if local (org-table-get-specials))
       (setq what (cond
+		  ((org-at-regexp-p "^@[0-9]+[ \t=]")
+		   (setq match (concat (substring (match-string 0) 0 -1)
+				       "$1.."
+				       (substring (match-string 0) 0 -1)
+				       "$100"))
+		   'range)
 		  ((or (org-at-regexp-p org-table-range-regexp2)
 		       (org-at-regexp-p org-table-translate-regexp)
 		       (org-at-regexp-p org-table-range-regexp))
@@ -4359,6 +4465,7 @@ list of the fields in the rectangle ."
 	  org-table-local-parameters org-table-named-field-locations
 	  org-table-current-line-types org-table-current-begin-line
 	  org-table-current-begin-pos org-table-dlines
+	  org-table-current-ncol
 	  org-table-hlines org-table-last-alignment
 	  org-table-last-column-widths org-table-last-alignment
 	  org-table-last-column-widths tbeg
@@ -4402,3 +4509,4 @@ list of the fields in the rectangle ."
 ;; arch-tag: 4d21cfdd-0268-440a-84b0-09237a0fe0ef
 
 ;;; org-table.el ends here
+