[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[O] [PATCH 8/9] obcalc: add more API, documentation and examples so tha
From: 
Jan Malakhovski 
Subject: 
[O] [PATCH 8/9] obcalc: add more API, documentation and examples so that it can be used in tables 
Date: 
Tue, 3 Nov 2015 20:15:46 +0000 
* lisp/obcalc.el (orgbabelcalceval):
(orgbabelcalcsetenv):
(orgbabelcalcresetenv):
(orgbabelcalcstoreenv):
(orgbabelcalcevalstring):
(orgbabelcalcevalline): New funcion.
(orgbabelexecute:calc): Rewrite to use new functions.
This also makes obcalc useful for computing complicated stuff in orgtables.
See
`orgbabelcalceval` docstring for more info.

lisp/obcalc.el  232 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 183 insertions(+), 49 deletions()
diff git a/lisp/obcalc.el b/lisp/obcalc.el
index a8c50da..e8b43e7 100644
 a/lisp/obcalc.el
+++ b/lisp/obcalc.el
@@ 1,4 +1,4 @@
;;; obcalc.el  Babel Functions for Calc * lexicalbinding: t;
*
+;;; obcalc.el  Babel Functions for Calc
;; Copyright (C) 20102015 Free Software Foundation, Inc.
@@ 23,7 +23,8 @@
;;; Commentary:
;; OrgBabel support for evaluating calc code
+;; OrgBabel and OrgTable support for evaluating calc code.
+;; See `orgbabelcalceval' for documentation.
;;; Code:
(require 'ob)
@@ 42,67 +43,200 @@
(defun orgbabelexpandbody:calc (body _params)
"Expand BODY according to PARAMS, return the expanded body." body)
(defvar orgvarsyms) ; Dynamically scoped from orgbabelexecute:calc

(defun orgbabelexecute:calc (body params)
"Execute a block of calc code with Babel."
+ (orgbabelcalceval (orgbabelexpandbody:calc body params)
+ (orgbabelgetvars params)))
+
+(defvar orgobcalcenvsymbol nil) ; For orgbabelcalceval
+(defvar orgobcalcvarnames nil)
+
+(defun orgbabelcalceval (text &optional environment envsymbol setup
envsetup)
+ "Evaluate TEXT as set of calc expressions (one per line) and return the top
of the stack.
+
+Optional argument ENVIRONMENT is a userdefined variables
+environment which is an alist of (SYMBOL . VALUE).
+
+Optional argument ENVSYMBOL is a symbol of a userdefined
+variables environment which is an alist of (SYMBOL . VALUE).
+
+Setting your environment using either of ENVIRONMENT or
+ENVSYMBOL has the same effect. The difference is that this
+function caches the value of ENVSYMBOL internally between
+succesive evaluations with ENVSYMBOL arguments of equal symbol
+names and reevaluates the value of ENVSYMBOL only when the
+symbol name of ENVSYMBOL changes.
+
+Additionally, setting ENVSYMBOL to nil will forget any
+internal environment before applying ENVIRONMENT, i.e. with
+ENVSYMBOL set to nil this function is pure.
+
+You can also use `orgbabelcalcsetenv',
+`orgbabelcalcresetenv' and `orgbabelcalcstoreenv' to set,
+reset and update the internal environment between evaluations.
+
+Optional argument SETUP allows additional calc setup on every
+evaluation.
+
+Optional argument ENVSETUP allows additional calc setup on every
+ENVSYMBOL change.
+
+This function is useful if you want to evaluate complicated
+formulas in a table, e.g. after evaluating
+
+ (setq anenv '((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='(orgbabelcalceval $1 anenv)
+
+which would become slow to recompute with a lot of rows, but then
+you can change the TBLFM line to
+
+ #+TBLFM: $2='(orgbabelcalceval $1 nil 'anenv)
+
+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='(orgbabelcalceval $1 nil 'anenv nil (lambda ()
(calcunitssimplifymode t)))
+
+In case that is not fast or complicated enough, you can combine
+this with `orgbabelcalcstoreenv' to produce some clever stuff
+like, e.g. computing environment on the fly (anenv variable is
+not actually used here, it is being generated just in case you
+want to use it elsewhere):
+
+ (setq anenv nil)
+ (defun computeandremember (name expr)
+ (let* ((v (orgbabelcalceval expr nil 'anenv nil (lambda ()
(calcunitssimplifymode t))))
+ (c `(,(intern name) . ,v)))
+ (orgbabelcalcstoreenv (list c))
+ (push c anenv)
+ v))
+
+and then
+
+  Name  Expr  Value 
+ ++
+  foo  2 day  2 day 
+  bar  foo + 6 hr  2.25 day 
+ ++
+ #+TBLFM: $3='(computeandremember $1 $2)
+
+Note that you can set ENVSYMBOL to 'nil to get ENVSETUP
+without.
+
+The subsequent results might become somewhat surprising in case
+ENVIRONMENT overrides variables set with ENVSYMBOL."
+ (orgbabelcalcinit)
+ (cond
+ ((equal envsymbol nil) (orgbabelcalcresetenv))
+ ((not (equal (symbolname envsymbol) orgobcalcenvsymbol))
+ (orgbabelcalcsetenv envsymbol)
+ (unless (null envsetup)
+ (funcall envsetup))))
+ (orgbabelcalcstoreenv environment)
+ (unless (null setup)
+ (funcall setup))
+ (orgbabelcalcevalstring text))
+
+(defun orgbabelcalcinit ()
+ "Initialize calc.
+
+You probably don't want to call this function explicitly."
(unless (getbuffer "*Calculator*")
 (savewindowexcursion (calc) (calcquit)))
 (let* ((vars (orgbabelgetvars params))
 (orgvarsyms (mapcar #'car vars))
 (varnames (mapcar #'symbolname orgvarsyms)))
 (mapc
 (lambda (pair)
 (calcpushlist (list (cdr pair)))
 (calcstoreinto (car pair)))
 vars)
 (mapc
 (lambda (line)
 (when (> (length line) 0)
 (cond
 ;; simple variable name
 ((member line varnames) (calcrecall (intern line)))
 ;; stack operation
 ((string= "'" (substring line 0 1))
 (funcall (lookupkey calcmodemap (substring line 1)) nil))
 ;; complex expression
 (t
 (calcpushlist
 (list (let ((res (calceval line)))
 (cond
 ((numberp res) res)
 ((mathreadnumber res) (mathreadnumber res))
 ((listp res) (error "Calc error \"%s\" on input \"%s\""
 (cadr res) line))
 (t (replaceregexpinstring
 "'" ""
 (calceval
 (mathevaluateexpr
 ;; resolve user variables, calc built in
 ;; variables are handled automatically
 ;; upstream by calc
 (mapcar #'orgbabelcalcmayberesolvevar
 ;; parse line into calc objects
 (car (mathreadexprs line)))))))))
 ))))))
 (mapcar #'orgbabeltrim
 (splitstring (orgbabelexpandbody:calc body params) "[\n\r]"))))
+ (savewindowexcursion (calc) (calcquit))))
+
+(defun orgbabelcalcsetenv (envsymbol)
+ "Force update current environment with the value of ENVSYMBOL.
+
+See `orgbabelcalceval' for more info."
+ (orgbabelcalcresetenv)
+ (orgbabelcalcstoreenv (eval envsymbol))
+ (setq orgobcalcenvsymbol (symbolname envsymbol)))
+
+(defun orgbabelcalcresetenv ()
+ "Forget current environment and the value of the last
+ENVSYMBOL.
+
+See `orgbabelcalceval' for more info."
+ (setq orgobcalcvarnames nil
+ orgobcalcenvsymbol nil))
+
+(defun orgbabelcalcstoreenv (vars)
+ "Store an environment (alist of (SYMBOL . VALUE) pairs) into calc.
+
+See `orgbabelcalceval' for more info."
+ (mapc
+ (lambda (pair)
+ (let ((name (symbolname (car pair)))
+ (value (cdr pair)))
+ ;; Using symbolname and then intern here may seem a little
+ ;; crazy, but without it calc may not recall some of variables
+ ;; that got noncanonical symbols, which will be very surprising
+ ;; for users that produce their environments with '(...) syntax.
+ ;; Better safe than sorry.
+ (calcstorevalue (intern name) value "" 0)
+ (push name orgobcalcvarnames)))
+ vars))
+
+(defun orgbabelcalcevalstring (text)
+ (mapc #'orgbabelcalcevalline (splitstring text "[\n\r]"))
(saveexcursion
(withcurrentbuffer (getbuffer "*Calculator*")
(calceval (calctop 1)))))
+(defun orgbabelcalcevalline (line)
+ (let ((line (orgbabeltrim line)))
+ (when (> (length line) 0)
+ (cond
+ ;; simple variable name
+ ((member line orgobcalcvarnames) (calcrecall (intern line)))
+ ;; stack operation
+ ((string= "'" (substring line 0 1))
+ (funcall (lookupkey calcmodemap (substring line 1)) nil))
+ ;; complex expression
+ (t (calcpushlist
+ (list (let ((res (calceval line)))
+ (cond
+ ((numberp res) res)
+ ((mathreadnumber res) (mathreadnumber res))
+ ((listp res) (error "Calc error \"%s\" on input \"%s\""
+ (cadr res) line))
+ (t (replaceregexpinstring "'" ""
+ (calceval
+ (mathevaluateexpr
+ ;; resolve user variables, calc built in
+ ;; variables are handled automatically
+ ;; upstream by calc
+ (mapcar #'orgbabelcalcmayberesolvevar
+ ;; parse line into calc objects
+ (car (mathreadexprs line))))))))))))))))
+
(defun orgbabelcalcmayberesolvevar (el)
(if (consp el)
 (if (and (equal 'var (car el)) (member (cadr el) orgvarsyms))
+ (if (and (equal 'var (car el))
+ (member (symbolname (cadr el)) orgobcalcvarnames))
(progn
(calcrecall (cadr el))
 (prog1 (calctop 1)
+ (prog1
+ (calctop 1)
(calcpop 1)))
 (mapcar #'orgbabelcalcmayberesolvevar el))
+ (mapcar #'orgbabelcalcmayberesolvevar el))
el))
(provide 'obcalc)


;;; obcalc.el ends here

2.6.2
 [O] [PATCH 4/9] org: move `orgdurationstringtominutes' to a better place, (continued)
 [O] [PATCH 4/9] org: move `orgdurationstringtominutes' to a better place, Jan Malakhovski, 2015/11/03
 [O] [PATCH 5/9] rename `orgdurationstringtominutes' to `orgclocksumstringtominutes' everywhere, Jan Malakhovski, 2015/11/03
 [O] [PATCH 6/9] factor out datetimestamp* calculations to orgstorelinkprops, Jan Malakhovski, 2015/11/03
 [O] [PATCH 7/9] orgnotmuch: add date support to orgnotmuchstorelink, Jan Malakhovski, 2015/11/03
 [O] [PATCH 8/9] obcalc: add more API, documentation and examples so that it can be used in tables,
Jan Malakhovski <=
 [O] [PATCH 9/9] obcalc: don't leave garbage on the stack, Jan Malakhovski, 2015/11/03
 Re: [O] [PATCH v2 0/9] mail, clock and calc changes, Aaron Ecay, 2015/11/04