[சனி நவம்பர் 08, 2025] Visuwesh wrote:
> A while back, we added the ability to pretty print tables and matrices.
> Similarly, it would be nice if we can format calc results as LaTeX (or
> more generally, as any calc-language). I have some working, not so
> tested, patch for the same but I would like to refrain from posting it
> until I am sure of my copyright situation wrt my employer.
>
> To make C-c C-x C-l format the LaTeX result, I need to force the
> :results to be raw. This introduces a world of pain since I now have to
> manually kill the old output before I can re-eval the source block. As
> an example, try C-c C-x C-l when the point is on
>
> : $$x + 1$$
>
> Nothing happens here. With :results raw, there is no colon in the front
> so I can preview the output.
>
> Note that I'm using the LaTeX preview rewrite branch, things might be
> different in the master branch.
>
> Emacs : GNU Emacs 31.0.50 (build 49, x86_64-pc-linux-gnu, Motif Version
> 2.3.8, cairo version 1.18.4)
> of 2025-04-24
> Package: Org mode version 9.8-pre (release_9.7.8-713-g62cbac @
> /home/viz/lib/emacs/straight/build/org/)
[ Didn't realise I started this thread in November: how time flies! ]
Please find attached patch. There is still a small problem with it that
I'm not sure how to resolve. Consider the following scenario:
1. Set Calc's language to Mathematica by doing C-x * * d M
2. Then with the patch applied, go to an Org mode buffer and write out
the following
#+BEGIN_SRC calc :language mathematica
B_12
#+END_SRC
3. Evaluate the source block and witness an error
This is because mathematica is not the right symbol for the Mathematica
calc language; it is math. In that case, the function
org-babel-calc--valid-lang-p returns nil and the block
(when lang
(setq old-lang calc-language
old-lang-opt calc-language-option)
;; Set the input language to be the normal language. For
;; instance, if Calc is set to Mathematica, then we want the
;; source block to accept syntax such as 'A_12'.
;; NOTE: The function call is required because side-effects are
;; used to update the formatting table internally used by calc.
;; This is still required if we had done
;; (calc-eval '(result-in-string-format calc-language latex))
;; because of the above issue. This is the equivalent of saying
;; d N (or something else). For similar reasons, let-binding
;; `calc-language' does not work appreciably.
(calc-set-language nil))
does not get evaluated so we never set the input Calc language to normal
when we read in the source block. However, if the same source block
header instead read
#+BEGIN_SRC calc :language math
then it would be evaluated to
B[[12]]
just as if you did
1. C-x * *
2. ' B_12 RET
3. d M
I could always set calc-language to normal regardless of the presence of
the :language parameter but that would be a breaking change. I think
erroring out when an incorrect language value is given is the better
solution, WDYT? Something like
Invalid language parameter; accepted values are c, pascal, ...
And here's the promised patch
diff --git a/lisp/ob-calc.el b/lisp/ob-calc.el
index 311a17c74..bf9deeb79 100644
--- a/lisp/ob-calc.el
+++ b/lisp/ob-calc.el
@@ -38,10 +38,17 @@ (require 'calc-trail)
(require 'calc-store)
(declare-function math-evaluate-expr "calc-ext" (x))
+(declare-function calc-set-language "calc-lang" (lang &optional option
no-refresh))
(defvar org-babel-default-header-args:calc nil
"Default arguments for evaluating a calc source block.")
+(defconst org-babel-header-args:calc
+ ;; There's no automatic way to retrieve this information.
+ '((language ( c pascal fortran tex latex eqn yacas maxima
+ giac math maple big)))
+ "Calc specific header arguments for Org babel.")
+
(defun org-babel-expand-body:calc (body params)
"Expand BODY according to PARAMS, return the expanded body."
(let ((prologue (cdr (assq :prologue params)))
@@ -53,13 +60,35 @@ (defun org-babel-expand-body:calc (body params)
(defvar org--var-syms) ; Dynamically scoped from org-babel-execute:calc
+(defun org-babel-calc--valid-lang-p (params)
+ "Return valid Calc language if given in PARAMS."
+ (and (assq :language params)
+ (car (memq (intern (cdr (assq :language params)))
+ (cadr (assq 'language org-babel-header-args:calc))))))
+
(defun org-babel-execute:calc (body params)
"Execute BODY of calc code with Babel using PARAMS."
(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)))
+ (var-names (mapcar #'symbol-name org--var-syms))
+ (lang (org-babel-calc--valid-lang-p params))
+ old-lang old-lang-opt)
+ (when lang
+ (setq old-lang calc-language
+ old-lang-opt calc-language-option)
+ ;; Set the input language to be the normal language. For
+ ;; instance, if Calc is set to Mathematica, then we want the
+ ;; source block to accept syntax such as 'A_12'.
+ ;; NOTE: The function call is required because side-effects are
+ ;; used to update the formatting table internally used by calc.
+ ;; This is still required if we had done
+ ;; (calc-eval '(result-in-string-format calc-language latex))
+ ;; because of the above issue. This is the equivalent of saying
+ ;; d N (or something else). For similar reasons, let-binding
+ ;; `calc-language' does not work appreciably.
+ (calc-set-language nil))
(mapc
(lambda (pair)
(let ((val (cdr pair)))
@@ -111,34 +140,55 @@ (defun org-babel-execute:calc (body params)
(car (math-read-exprs line)))))))))
))))))
(mapcar #'org-trim
- (split-string (org-babel-expand-body:calc body params) "[\n\r]"))))
- (let ((result (prog1
- ;; Cannot use 'top' as SEPARATOR reliably when the
- ;; top of the stack has a vector.
- (calc-eval (calc-top 1) 'raw)
- (calc-eval 1 'pop)))
- (calc-line-numbering)
- lisp-table)
- (org-babel-reassemble-table
- (org-babel-result-cond (cdr (assq :result-params params))
- (calc-eval result)
- (if (Math-vectorp result)
- (progn
- (dolist (r (if (math-matrixp result)
- (cdr result) ; Ignore the 'vec item.
- (list result)))
- (setq r (cdr r)) ; Ignore the 'vec item.
- (push (mapcar (lambda (x) (math-format-stack-value (list x 1
nil))) r)
- lisp-table))
- (setq lisp-table (nreverse lisp-table)))
- (calc-eval result)))
- (org-babel-pick-name
- (cdr (assq :colname-names params)) (cdr (assq :colnames params)))
- (org-babel-pick-name
- (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))
+ (split-string (org-babel-expand-body:calc body params) "[\n\r]")))
+ (let ((result (prog1
+ ;; Cannot use 'top' as SEPARATOR reliably when the
+ ;; top of the stack has a vector.
+ (calc-eval (calc-top 1) 'raw)
+ (calc-eval 1 'pop)))
+ (calc-line-numbering)
+ lisp-table)
+ (if lang
+ (org-babel-calc--format-lang result params old-lang old-lang-opt)
+ (org-babel-reassemble-table
+ (org-babel-result-cond (cdr (assq :result-params params))
+ (calc-eval result)
+ (if (Math-vectorp result)
+ (progn
+ (dolist (r (if (math-matrixp result)
+ (cdr result) ; Ignore the 'vec item.
+ (list result)))
+ (setq r (cdr r)) ; Ignore the 'vec item.
+ (push (mapcar (lambda (x) (math-format-stack-value (list x
1 nil))) r)
+ lisp-table))
+ (setq lisp-table (nreverse lisp-table)))
+ (calc-eval result)))
+ (org-babel-pick-name
+ (cdr (assq :colname-names params)) (cdr (assq :colnames params)))
+ (org-babel-pick-name
+ (cdr (assq :rowname-names params)) (cdr (assq :rownames
params))))))))
+
+(defun org-babel-calc--format-lang (result params old-lang old-lang-opt)
+ "Return RESULT as per the Calc language given in PARAMS.
+This resets the Calc language back to OLD-LANG, and the options to
+OLD-LANG-OPT."
+ (calc-set-language (org-babel-calc--valid-lang-p params))
+ (prog1
+ (if (eq calc-language 'latex)
+ (concat "\\begin{equation*}\n"
+ (calc-eval result)
+ "\n\\end{equation*}")
+ (calc-eval result))
+ (calc-set-language old-lang old-lang-opt)
+ ;; This is needed so that we can force Org to print the results as
+ ;; "raw" format. If there's a colon in front, then C-c C-x C-l
+ ;; does not do the job anymore.
+ (when (equal "latex" (cdr (assq :language params)))
+ (cl-callf (lambda (v) (nconc v '("raw")))
+ (alist-get :result-params params)))))
(defun org-babel-calc-maybe-resolve-var (el)
-"Resolve user variables in EL.
+ "Resolve user variables in EL.
EL is taken from the output of `math-read-exprs'."
(if (consp el)
(if (and (eq 'var (car el)) (member (cadr el) org--var-syms))
diff --git a/testing/lisp/test-ob-calc.el b/testing/lisp/test-ob-calc.el
index de6656a4e..8ce1a9bee 100644
--- a/testing/lisp/test-ob-calc.el
+++ b/testing/lisp/test-ob-calc.el
@@ -160,5 +160,32 @@ (ert-deftest ob-calc/conv-matrix ()
(should (equal '(("2" "4") ("4.4" "8.4"))
(org-babel-execute-src-block)))))
+(ert-deftest ob-calc/lang-latex-frac ()
+ "Test :language latex header for fraction output."
+ (org-test-with-temp-text "\
+<point>#+BEGIN_SRC calc :language latex
+4:5
+#+END_SRC"
+ (should (equal "\\begin{equation*}\n\\frac{4}{5}\n\\end{equation*}"
+ (org-babel-execute-src-block)))))
+
+(ert-deftest ob-calc/lang-fortran-list ()
+ "Test :language fortran header for list output."
+ (org-test-with-temp-text "\
+<point>#+BEGIN_SRC calc :language fortran
+[ 1, 2, 3 ]
+#+END_SRC"
+ (should (equal "/1, 2, 3/"
+ (org-babel-execute-src-block)))))
+
+(ert-deftest ob-calc/lang-invalid-list ()
+ "Test that invalid :language header returns normal output."
+ (org-test-with-temp-text "\
+<point>#+BEGIN_SRC calc :language asdf
+[ 1, 2, 3 ]
+#+END_SRC"
+ (should (equal '(("1" "2" "3"))
+ (org-babel-execute-src-block)))))
+
(provide 'test-ob-calc)
;;; test-ob-calc.el ends here