;;; ob-calc.el --- Babel Functions for Calc ;; Copyright (C) 2010-2015 Free Software Foundation, Inc. ;; Author: Eric Schulte ;; Keywords: literate programming, reproducible research ;; Homepage: http://orgmode.org ;; This file is part of GNU Emacs. ;; GNU Emacs 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 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs 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. If not, see . ;;; Commentary: ;; Org-Babel and Org-Table support for evaluating calc code. ;; See `org-babel-calc-eval' for documentation. ;;; Code: (require 'ob) (require 'calc) (unless (featurep 'xemacs) (require 'calc-trail) (require 'calc-store)) (declare-function calc-store-into "calc-store" (&optional var)) (declare-function calc-recall "calc-store" (&optional var)) (declare-function math-evaluate-expr "calc-ext" (x)) (defvar org-babel-default-header-args:calc nil "Default arguments for evaluating an calc source block.") (defun org-babel-expand-body:calc (body _params) "Expand BODY according to PARAMS, return the expanded body." body) (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)))) (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 (symbol-name (cadr el)) org--ob-calc-var-names)) (progn (calc-recall (cadr el)) (prog1 (calc-top 1) (calc-pop 1))) (mapcar #'org-babel-calc-maybe-resolve-var el)) el)) (provide 'ob-calc) ;;; ob-calc.el ends here