ob-octave.el (10152B)
1 ;;; ob-octave.el --- Babel Functions for Octave and Matlab -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2010-2024 Free Software Foundation, Inc. 4 5 ;; Author: Dan Davison 6 ;; Keywords: literate programming, reproducible research 7 ;; URL: https://orgmode.org 8 9 ;; This file is part of GNU Emacs. 10 11 ;; GNU Emacs is free software: you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; GNU Emacs is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;;; Requirements: 27 28 ;; octave 29 ;; octave-mode.el and octave-inf.el come with GNU emacs 30 31 ;;; Code: 32 33 (require 'org-macs) 34 (org-assert-version) 35 36 (require 'ob) 37 (require 'org-macs) 38 39 (declare-function matlab-shell "ext:matlab-mode") 40 (declare-function matlab-shell-run-region "ext:matlab-mode") 41 42 (defvar org-babel-default-header-args:matlab '()) 43 (defvar org-babel-default-header-args:octave '()) 44 45 (defvar org-babel-matlab-shell-command "matlab -nosplash" 46 "Shell command to run matlab as an external process.") 47 (defvar org-babel-octave-shell-command "octave -q" 48 "Shell command to run octave as an external process.") 49 50 (defvar org-babel-matlab-with-emacs-link nil 51 "If non-nil use matlab-shell-run-region for session evaluation. 52 This will use EmacsLink if (matlab-with-emacs-link) evaluates 53 to a non-nil value.") 54 55 (defvar org-babel-matlab-emacs-link-wrapper-method 56 "%s 57 if ischar(ans), fid = fopen('%s', 'w'); fprintf(fid, '%%s\\n', ans); fclose(fid); 58 else, save -ascii %s ans 59 end 60 delete('%s') 61 ") 62 (defvar org-babel-octave-wrapper-method 63 "%s 64 if ischar(ans), fid = fopen('%s', 'w'); fdisp(fid, ans); fclose(fid); 65 else, dlmwrite('%s', ans, '\\t') 66 end") 67 68 (defvar org-babel-octave-eoe-indicator "'org_babel_eoe'") 69 70 (defvar org-babel-octave-eoe-output "ans = org_babel_eoe") 71 72 (defun org-babel-execute:matlab (body params) 73 "Execute Matlab BODY according to PARAMS." 74 (org-babel-execute:octave body params 'matlab)) 75 76 (defun org-babel-execute:octave (body params &optional matlabp) 77 "Execute Octave or Matlab BODY according to PARAMS. 78 When MATLABP is non-nil, execute Matlab. Otherwise, execute Octave." 79 (let* ((session 80 (funcall (intern (format "org-babel-%s-initiate-session" 81 (if matlabp "matlab" "octave"))) 82 (cdr (assq :session params)) params)) 83 (result-type (cdr (assq :result-type params))) 84 (full-body 85 (org-babel-expand-body:generic 86 body params (org-babel-variable-assignments:octave params))) 87 (gfx-file (ignore-errors (org-babel-graphical-output-file params))) 88 (result (org-babel-octave-evaluate 89 session 90 (if gfx-file 91 (mapconcat 'identity 92 (list 93 "set (0, \"defaultfigurevisible\", \"off\");" 94 full-body 95 (format "print -dpng %S\nans=%S" gfx-file gfx-file)) 96 "\n") 97 full-body) 98 result-type matlabp))) 99 (if gfx-file 100 nil 101 (org-babel-reassemble-table 102 result 103 (org-babel-pick-name 104 (cdr (assq :colname-names params)) (cdr (assq :colnames params))) 105 (org-babel-pick-name 106 (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))) 107 108 (defun org-babel-prep-session:matlab (session params) 109 "Prepare SESSION according to PARAMS." 110 (org-babel-prep-session:octave session params 'matlab)) 111 112 (defun org-babel-variable-assignments:octave (params) 113 "Return list of octave statements assigning the block's variables. 114 The variables are taken from PARAMS." 115 (mapcar 116 (lambda (pair) 117 (format "%s=%s;" 118 (car pair) 119 (org-babel-octave-var-to-octave (cdr pair)))) 120 (org-babel--get-vars params))) 121 122 (defalias 'org-babel-variable-assignments:matlab 123 'org-babel-variable-assignments:octave) 124 125 (defun org-babel-octave-var-to-octave (value) 126 "Convert an emacs-lisp VALUE into an octave variable. 127 Converts an emacs-lisp variable into a string of octave code 128 specifying a variable of the same value." 129 (if (listp value) 130 (concat "[" (mapconcat #'org-babel-octave-var-to-octave value 131 (if (listp (car value)) "; " ",")) "]") 132 (cond 133 ((stringp value) 134 (format "'%s'" value)) 135 (t 136 (format "%s" value))))) 137 138 (defun org-babel-prep-session:octave (session params &optional matlabp) 139 "Prepare SESSION according to the header arguments specified in PARAMS. 140 The session will be an Octave session, unless MATLABP is non-nil." 141 (let* ((session (org-babel-octave-initiate-session session params matlabp)) 142 (var-lines (org-babel-variable-assignments:octave params))) 143 (org-babel-comint-in-buffer session 144 (mapc (lambda (var) 145 (end-of-line 1) (insert var) (comint-send-input nil t) 146 (org-babel-comint-wait-for-output session)) 147 var-lines)) 148 session)) 149 150 (defun org-babel-matlab-initiate-session (&optional session params) 151 "Create a matlab inferior process buffer. 152 If there is not a current inferior-process-buffer in SESSION then 153 create. Return the initialized session. PARAMS are src block parameters." 154 (org-babel-octave-initiate-session session params 'matlab)) 155 156 (defun org-babel-octave-initiate-session (&optional session _params matlabp) 157 "Create an octave inferior process buffer. 158 If there is not a current inferior-process-buffer in SESSION then 159 create. Return the initialized session. The session will be an 160 Octave session, unless MATLABP is non-nil." 161 (if matlabp 162 (org-require-package 'matlab "matlab-mode") 163 (or (require 'octave-inf nil 'noerror) 164 (require 'octave))) 165 (unless (string= session "none") 166 (let ((session (or session 167 (if matlabp "*Inferior Matlab*" "*Inferior Octave*")))) 168 (if (org-babel-comint-buffer-livep session) session 169 (save-window-excursion 170 (if matlabp (unless org-babel-matlab-with-emacs-link (matlab-shell)) 171 (run-octave)) 172 (rename-buffer (if (bufferp session) (buffer-name session) 173 (if (stringp session) session (buffer-name)))) 174 (current-buffer)))))) 175 176 (defun org-babel-octave-evaluate 177 (session body result-type &optional matlabp) 178 "Pass BODY to the octave process in SESSION. 179 If RESULT-TYPE equals `output' then return the outputs of the 180 statements in BODY, if RESULT-TYPE equals `value' then return the 181 value of the last statement in BODY, as elisp." 182 (if session 183 (org-babel-octave-evaluate-session session body result-type matlabp) 184 (org-babel-octave-evaluate-external-process body result-type matlabp))) 185 186 (defun org-babel-octave-evaluate-external-process (body result-type matlabp) 187 "Evaluate BODY in an external Octave or Matalab process. 188 Process the result as RESULT-TYPE. Use Octave, unless MATLABP is non-nil." 189 (let ((cmd (if matlabp 190 org-babel-matlab-shell-command 191 org-babel-octave-shell-command))) 192 (pcase result-type 193 (`output (org-babel-eval cmd body)) 194 (`value (let ((tmp-file (org-babel-temp-file "octave-"))) 195 (org-babel-eval 196 cmd 197 (format org-babel-octave-wrapper-method body 198 (org-babel-process-file-name tmp-file 'noquote) 199 (org-babel-process-file-name tmp-file 'noquote))) 200 (org-babel-octave-import-elisp-from-file tmp-file)))))) 201 202 (defun org-babel-octave-evaluate-session 203 (session body result-type &optional matlabp) 204 "Evaluate BODY in SESSION." 205 (let* ((tmp-file (org-babel-temp-file (if matlabp "matlab-" "octave-"))) 206 (wait-file (org-babel-temp-file "matlab-emacs-link-wait-signal-")) 207 (full-body 208 (pcase result-type 209 (`output 210 (mapconcat 211 #'org-babel-chomp 212 (list body org-babel-octave-eoe-indicator) "\n")) 213 (`value 214 (if (and matlabp org-babel-matlab-with-emacs-link) 215 (concat 216 (format org-babel-matlab-emacs-link-wrapper-method 217 body 218 (org-babel-process-file-name tmp-file 'noquote) 219 (org-babel-process-file-name tmp-file 'noquote) wait-file) "\n") 220 (mapconcat 221 #'org-babel-chomp 222 (list (format org-babel-octave-wrapper-method 223 body 224 (org-babel-process-file-name tmp-file 'noquote) 225 (org-babel-process-file-name tmp-file 'noquote)) 226 org-babel-octave-eoe-indicator) "\n"))))) 227 (raw (if (and matlabp org-babel-matlab-with-emacs-link) 228 (save-window-excursion 229 (with-temp-buffer 230 (insert full-body) 231 (write-region "" 'ignored wait-file nil nil nil 'excl) 232 (matlab-shell-run-region (point-min) (point-max)) 233 (message "Waiting for Matlab Emacs Link") 234 (while (file-exists-p wait-file) (sit-for 0.01)) 235 "")) ;; matlab-shell-run-region doesn't seem to 236 ;; make *matlab* buffer contents easily 237 ;; available, so :results output currently 238 ;; won't work 239 (org-babel-comint-with-output 240 (session 241 (if matlabp 242 org-babel-octave-eoe-indicator 243 org-babel-octave-eoe-output) 244 t full-body) 245 (insert full-body) (comint-send-input nil t)))) 246 results) 247 (pcase result-type 248 (`value 249 (org-babel-octave-import-elisp-from-file tmp-file)) 250 (`output 251 (setq results 252 (if matlabp 253 (cdr (reverse (delete "" (mapcar #'org-strip-quotes 254 (mapcar #'org-trim raw))))) 255 (cdr (member org-babel-octave-eoe-output 256 (reverse (mapcar #'org-strip-quotes 257 (mapcar #'org-trim raw))))))) 258 (mapconcat #'identity (reverse results) "\n"))))) 259 260 (defun org-babel-octave-import-elisp-from-file (file-name) 261 "Import data from FILE-NAME. 262 This removes initial blank and comment lines and then calls 263 `org-babel-import-elisp-from-file'." 264 (let ((temp-file (org-babel-temp-file "octave-matlab-")) beg end) 265 (with-temp-file temp-file 266 (insert-file-contents file-name) 267 (re-search-forward "^[ \t]*[^# \t]" nil t) 268 (when (< (setq beg (point-min)) 269 (setq end (line-beginning-position))) 270 (delete-region beg end))) 271 (org-babel-import-elisp-from-file temp-file '(16)))) 272 273 (provide 'ob-octave) 274 275 ;;; ob-octave.el ends here