emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Jan Malakhovski <oxij@oxij.org>
To: emacs-orgmode@gnu.org
Cc: Jan Malakhovski <oxij@oxij.org>
Subject: [PATCH 8/9] ob-calc: add more API, documentation and examples so that it can be used in tables
Date: Tue,  3 Nov 2015 20:15:46 +0000	[thread overview]
Message-ID: <1446581747-1960-9-git-send-email-oxij@oxij.org> (raw)
In-Reply-To: <1446581747-1960-1-git-send-email-oxij@oxij.org>

* lisp/ob-calc.el (org-babel-calc-eval):
(org-babel-calc-set-env):
(org-babel-calc-reset-env):
(org-babel-calc-store-env):
(org-babel-calc-eval-string):
(org-babel-calc-eval-line): New funcion.
(org-babel-execute:calc): Rewrite to use new functions.

This also makes ob-calc useful for computing complicated stuff in org-tables. See
`org-babel-calc-eval` docstring for more info.
---
 lisp/ob-calc.el | 232 ++++++++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 183 insertions(+), 49 deletions(-)

diff --git a/lisp/ob-calc.el b/lisp/ob-calc.el
index a8c50da..e8b43e7 100644
--- a/lisp/ob-calc.el
+++ b/lisp/ob-calc.el
@@ -1,4 +1,4 @@
-;;; ob-calc.el --- Babel Functions for Calc          -*- lexical-binding: t; -*-
+;;; ob-calc.el --- Babel Functions for Calc
 
 ;; Copyright (C) 2010-2015 Free Software Foundation, Inc.
 
@@ -23,7 +23,8 @@
 
 ;;; Commentary:
 
-;; Org-Babel support for evaluating calc code
+;; Org-Babel and Org-Table support for evaluating calc code.
+;; See `org-babel-calc-eval' for documentation.
 
 ;;; Code:
 (require 'ob)
@@ -42,67 +43,200 @@
 (defun org-babel-expand-body:calc (body _params)
   "Expand BODY according to PARAMS, return the expanded body." body)
 
-(defvar org--var-syms) ; Dynamically scoped from org-babel-execute:calc
-
 (defun org-babel-execute:calc (body params)
   "Execute a block of calc code with Babel."
+  (org-babel-calc-eval (org-babel-expand-body:calc body params)
+		       (org-babel--get-vars params)))
+
+(defvar org--ob-calc-env-symbol nil) ; For org-babel-calc-eval
+(defvar org--ob-calc-var-names nil)
+
+(defun org-babel-calc-eval (text &optional environment env-symbol setup env-setup)
+  "Evaluate TEXT as set of calc expressions (one per line) and return the top of the stack.
+
+Optional argument ENVIRONMENT is a user-defined variables
+environment which is an alist of (SYMBOL . VALUE).
+
+Optional argument ENV-SYMBOL is a symbol of a user-defined
+variables environment which is an alist of (SYMBOL . VALUE).
+
+Setting your environment using either of ENVIRONMENT or
+ENV-SYMBOL has the same effect. The difference is that this
+function caches the value of ENV-SYMBOL internally between
+succesive evaluations with ENV-SYMBOL arguments of equal symbol
+names and reevaluates the value of ENV-SYMBOL only when the
+symbol name of ENV-SYMBOL changes.
+
+Additionally, setting ENV-SYMBOL to nil will forget any
+internal environment before applying ENVIRONMENT, i.e. with
+ENV-SYMBOL set to nil this function is pure.
+
+You can also use `org-babel-calc-set-env',
+`org-babel-calc-reset-env' and `org-babel-calc-store-env' to set,
+reset and update the internal environment between evaluations.
+
+Optional argument SETUP allows additional calc setup on every
+evaluation.
+
+Optional argument ENV-SETUP allows additional calc setup on every
+ENV-SYMBOL change.
+
+This function is useful if you want to evaluate complicated
+formulas in a table, e.g. after evaluating
+
+  (setq an-env '((foo . \"2 day\")
+                 (bar . \"6 hr\")))
+
+you can use this in the following table
+
+  | Expr      | Result       |
+  |-----------+--------------|
+  | foo + bar | 2 day + 6 hr |
+  | foo - bar | 2 day - 6 hr |
+  |-----------+--------------|
+  #+TBLFM: $2='(org-babel-calc-eval $1 an-env)
+
+which would become slow to recompute with a lot of rows, but then
+you can change the TBLFM line to
+
+  #+TBLFM: $2='(org-babel-calc-eval $1 nil 'an-env)
+
+and it would become fast again.
+
+SETUP argument can be used like this:
+
+  | Expr      | Result   |
+  |-----------+----------|
+  | foo + bar | 2.25 day |
+  | foo - bar | 1.75 day |
+  |-----------+----------|
+  #+TBLFM: $2='(org-babel-calc-eval $1 nil 'an-env nil (lambda () (calc-units-simplify-mode t)))
+
+In case that is not fast or complicated enough, you can combine
+this with `org-babel-calc-store-env' to produce some clever stuff
+like, e.g. computing environment on the fly (an-env variable is
+not actually used here, it is being generated just in case you
+want to use it elsewhere):
+
+  (setq an-env nil)
+  (defun compute-and-remember (name expr)
+    (let* ((v (org-babel-calc-eval expr nil 'an-env nil (lambda () (calc-units-simplify-mode t))))
+           (c `(,(intern name) . ,v)))
+        (org-babel-calc-store-env (list c))
+        (push c an-env)
+        v))
+
+and then
+
+  | Name | Expr       | Value    |
+  |------+------------+----------|
+  | foo  | 2 day      | 2 day    |
+  | bar  | foo + 6 hr | 2.25 day |
+  |------+------------+----------|
+  #+TBLFM: $3='(compute-and-remember $1 $2)
+
+Note that you can set ENV-SYMBOL to 'nil to get ENV-SETUP
+without.
+
+The subsequent results might become somewhat surprising in case
+ENVIRONMENT overrides variables set with ENV-SYMBOL."
+  (org-babel-calc-init)
+  (cond
+    ((equal env-symbol nil) (org-babel-calc-reset-env))
+    ((not (equal (symbol-name env-symbol) org--ob-calc-env-symbol))
+	(org-babel-calc-set-env env-symbol)
+	(unless (null env-setup)
+	  (funcall env-setup))))
+  (org-babel-calc-store-env environment)
+  (unless (null setup)
+    (funcall setup))
+  (org-babel-calc-eval-string text))
+
+(defun org-babel-calc-init ()
+  "Initialize calc.
+
+You probably don't want to call this function explicitly."
   (unless (get-buffer "*Calculator*")
-    (save-window-excursion (calc) (calc-quit)))
-  (let* ((vars (org-babel--get-vars params))
-	 (org--var-syms (mapcar #'car vars))
-	 (var-names (mapcar #'symbol-name org--var-syms)))
-    (mapc
-     (lambda (pair)
-       (calc-push-list (list (cdr pair)))
-       (calc-store-into (car pair)))
-     vars)
-    (mapc
-     (lambda (line)
-       (when (> (length line) 0)
-	 (cond
-	  ;; simple variable name
-	  ((member line var-names) (calc-recall (intern line)))
-	  ;; stack operation
-	  ((string= "'" (substring line 0 1))
-	   (funcall (lookup-key calc-mode-map (substring line 1)) nil))
-	  ;; complex expression
-	  (t
-	   (calc-push-list
-	    (list (let ((res (calc-eval line)))
-                    (cond
-                     ((numberp res) res)
-                     ((math-read-number res) (math-read-number res))
-                     ((listp res) (error "Calc error \"%s\" on input \"%s\""
-                                         (cadr res) line))
-                     (t (replace-regexp-in-string
-                         "'" ""
-                         (calc-eval
-                          (math-evaluate-expr
-                           ;; resolve user variables, calc built in
-                           ;; variables are handled automatically
-                           ;; upstream by calc
-                           (mapcar #'org-babel-calc-maybe-resolve-var
-                                   ;; parse line into calc objects
-                                   (car (math-read-exprs line)))))))))
-                  ))))))
-     (mapcar #'org-babel-trim
-	     (split-string (org-babel-expand-body:calc body params) "[\n\r]"))))
+    (save-window-excursion (calc) (calc-quit))))
+
+(defun org-babel-calc-set-env (env-symbol)
+  "Force update current environment with the value of ENV-SYMBOL.
+
+See `org-babel-calc-eval' for more info."
+  (org-babel-calc-reset-env)
+  (org-babel-calc-store-env (eval env-symbol))
+  (setq org--ob-calc-env-symbol (symbol-name env-symbol)))
+
+(defun org-babel-calc-reset-env ()
+  "Forget current environment and the value of the last
+ENV-SYMBOL.
+
+See `org-babel-calc-eval' for more info."
+  (setq org--ob-calc-var-names nil
+	org--ob-calc-env-symbol nil))
+
+(defun org-babel-calc-store-env (vars)
+  "Store an environment (alist of (SYMBOL . VALUE) pairs) into calc.
+
+See `org-babel-calc-eval' for more info."
+  (mapc
+    (lambda (pair)
+	(let ((name (symbol-name (car pair)))
+	    (value (cdr pair)))
+	;; Using symbol-name and then intern here may seem a little
+	;; crazy, but without it calc may not recall some of variables
+	;; that got non-canonical symbols, which will be very surprising
+	;; for users that produce their environments with '(...) syntax.
+	;; Better safe than sorry.
+	  (calc-store-value (intern name) value "" 0)
+	  (push name org--ob-calc-var-names)))
+    vars))
+
+(defun org-babel-calc-eval-string (text)
+  (mapc #'org-babel-calc-eval-line (split-string text "[\n\r]"))
   (save-excursion
     (with-current-buffer (get-buffer "*Calculator*")
       (calc-eval (calc-top 1)))))
 
+(defun org-babel-calc-eval-line (line)
+  (let ((line (org-babel-trim line)))
+    (when (> (length line) 0)
+	(cond
+	 ;; simple variable name
+	 ((member line org--ob-calc-var-names) (calc-recall (intern line)))
+	 ;; stack operation
+	 ((string= "'" (substring line 0 1))
+	(funcall (lookup-key calc-mode-map (substring line 1)) nil))
+	 ;; complex expression
+	 (t (calc-push-list
+	 (list (let ((res (calc-eval line)))
+		 (cond
+		   ((numberp res) res)
+		   ((math-read-number res) (math-read-number res))
+		   ((listp res) (error "Calc error \"%s\" on input \"%s\""
+					 (cadr res) line))
+		   (t (replace-regexp-in-string "'" ""
+			(calc-eval
+			  (math-evaluate-expr
+				;; resolve user variables, calc built in
+				;; variables are handled automatically
+				;; upstream by calc
+				(mapcar #'org-babel-calc-maybe-resolve-var
+				;; parse line into calc objects
+				(car (math-read-exprs line))))))))))))))))
+
 (defun org-babel-calc-maybe-resolve-var (el)
   (if (consp el)
-      (if (and (equal 'var (car el)) (member (cadr el) org--var-syms))
+	(if (and (equal 'var (car el))
+		 (member (symbol-name (cadr el)) org--ob-calc-var-names))
 	  (progn
 	    (calc-recall (cadr el))
-	    (prog1 (calc-top 1)
+	    (prog1
+	      (calc-top 1)
 	      (calc-pop 1)))
-	(mapcar #'org-babel-calc-maybe-resolve-var el))
+	  (mapcar #'org-babel-calc-maybe-resolve-var el))
     el))
 
 (provide 'ob-calc)
 
-
-
 ;;; ob-calc.el ends here
-- 
2.6.2

  parent reply	other threads:[~2015-11-03 20:16 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2015-11-03 20:15 [PATCH v2 0/9] mail, clock and calc changes Jan Malakhovski
2015-11-03 20:15 ` [PATCH 1/9] org-clock: fix a typo Jan Malakhovski
2015-11-03 20:15 ` [PATCH 2/9] org-colview: add a FIXME Jan Malakhovski
2015-11-03 20:15 ` [PATCH 3/9] org-clock: fix `org-clock-time%' Jan Malakhovski
2015-11-04 11:18   ` Aaron Ecay
2015-11-04 11:46     ` Jan Malakhovski
2015-11-03 20:15 ` [PATCH 4/9] org: move `org-duration-string-to-minutes' to a better place Jan Malakhovski
2015-11-03 20:15 ` [PATCH 5/9] rename `org-duration-string-to-minutes' to `org-clocksum-string-to-minutes' everywhere Jan Malakhovski
2015-11-04 11:21   ` Aaron Ecay
2015-11-04 11:47     ` Jan Malakhovski
2015-11-03 20:15 ` [PATCH 6/9] factor out date-timestamp* calculations to org-store-link-props Jan Malakhovski
2015-11-04 11:26   ` Aaron Ecay
2015-11-04 11:45     ` Jan Malakhovski
2015-11-04 14:39       ` Aaron Ecay
2015-11-04 15:21         ` Jan Malakhovski
2015-11-03 20:15 ` [PATCH 7/9] org-notmuch: add date support to org-notmuch-store-link Jan Malakhovski
2015-11-03 20:15 ` Jan Malakhovski [this message]
2015-11-03 20:15 ` [PATCH 9/9] ob-calc: don't leave garbage on the stack Jan Malakhovski
2015-11-04 11:31   ` Aaron Ecay
2015-11-04 11:53     ` Jan Malakhovski
2015-11-04 14:41       ` Aaron Ecay
2015-11-04 15:24         ` Jan Malakhovski
2015-11-04 11:36 ` [PATCH v2 0/9] mail, clock and calc changes Aaron Ecay
2015-11-04 11:59   ` Jan Malakhovski

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1446581747-1960-9-git-send-email-oxij@oxij.org \
    --to=oxij@oxij.org \
    --cc=emacs-orgmode@gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).