Update copyright years again.
[org-mode.git] / contrib / lisp / ob-julia.el
blob4d8deb28facc993b9c1b38fcd9f10fdfd464c69c
1 ;;; ob-julia.el --- org-babel functions for julia code evaluation
3 ;; Copyright (C) 2013, 2014 G. Jay Kerns
4 ;; Author: G. Jay Kerns, based on ob-R.el by Eric Schulte and Dan Davison
6 ;; This file is not part of GNU Emacs.
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 3, or (at your option)
11 ;; any later version.
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with GNU Emacs; see the file COPYING. If not, write to the
20 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 ;; Boston, MA 02110-1301, USA.
23 ;;; Commentary:
25 ;; The file provides Org-Babel support for evaluating julia code.
27 ;; See https://github.com/gjkerns/ob-julia/blob/master/ob-julia-doc.org
28 ;; for detailed instructions on how to get started. The git repository
29 ;; contains more documentation: git://github.com/gjkerns/ob-julia.git
31 ;;; Code:
32 (require 'ob)
33 (eval-when-compile (require 'cl))
35 (declare-function orgtbl-to-csv "org-table" (table params))
36 (declare-function julia "ext:ess-julia" (&optional start-args))
37 (declare-function inferior-ess-send-input "ext:ess-inf" ())
38 (declare-function ess-make-buffer-current "ext:ess-inf" ())
39 (declare-function ess-eval-buffer "ext:ess-inf" (vis))
40 (declare-function org-number-sequence "org-compat" (from &optional to inc))
41 (declare-function org-remove-if-not "org" (predicate seq))
43 (defconst org-babel-header-args:julia
44 '((width . :any)
45 (horizontal . :any)
46 (results . ((file list vector table scalar verbatim)
47 (raw org html latex code pp wrap)
48 (replace silent append prepend)
49 (output value graphics))))
50 "julia-specific header arguments.")
52 (add-to-list 'org-babel-tangle-lang-exts '("julia" . "jl"))
54 (defvar org-babel-default-header-args:julia '())
56 (defcustom org-babel-julia-command inferior-julia-program-name
57 "Name of command to use for executing julia code."
58 :group 'org-babel
59 :version "24.4"
60 :package-version '(Org . "8.0")
61 :type 'string)
63 (defvar ess-local-process-name) ; dynamically scoped
64 (defun org-babel-edit-prep:julia (info)
65 (let ((session (cdr (assoc :session (nth 2 info)))))
66 (when (and session (string-match "^\\*\\(.+?\\)\\*$" session))
67 (save-match-data (org-babel-julia-initiate-session session nil)))))
69 (defun org-babel-expand-body:julia (body params &optional graphics-file)
70 "Expand BODY according to PARAMS, return the expanded body."
71 (let ((graphics-file
72 (or graphics-file (org-babel-julia-graphical-output-file params))))
73 (mapconcat
74 #'identity
75 ((lambda (inside)
76 (if graphics-file
77 inside
78 inside))
79 (append (org-babel-variable-assignments:julia params)
80 (list body))) "\n")))
82 (defun org-babel-execute:julia (body params)
83 "Execute a block of julia code.
84 This function is called by `org-babel-execute-src-block'."
85 (save-excursion
86 (let* ((result-params (cdr (assoc :result-params params)))
87 (result-type (cdr (assoc :result-type params)))
88 (session (org-babel-julia-initiate-session
89 (cdr (assoc :session params)) params))
90 (colnames-p (cdr (assoc :colnames params)))
91 (rownames-p (cdr (assoc :rownames params)))
92 (graphics-file (org-babel-julia-graphical-output-file params))
93 (full-body (org-babel-expand-body:julia body params graphics-file))
94 (result
95 (org-babel-julia-evaluate
96 session full-body result-type result-params
97 (or (equal "yes" colnames-p)
98 (org-babel-pick-name
99 (cdr (assoc :colname-names params)) colnames-p))
100 (or (equal "yes" rownames-p)
101 (org-babel-pick-name
102 (cdr (assoc :rowname-names params)) rownames-p)))))
103 (if graphics-file nil result))))
105 (defun org-babel-prep-session:julia (session params)
106 "Prepare SESSION according to the header arguments specified in PARAMS."
107 (let* ((session (org-babel-julia-initiate-session session params))
108 (var-lines (org-babel-variable-assignments:julia params)))
109 (org-babel-comint-in-buffer session
110 (mapc (lambda (var)
111 (end-of-line 1) (insert var) (comint-send-input nil t)
112 (org-babel-comint-wait-for-output session)) var-lines))
113 session))
115 (defun org-babel-load-session:julia (session body params)
116 "Load BODY into SESSION."
117 (save-window-excursion
118 (let ((buffer (org-babel-prep-session:julia session params)))
119 (with-current-buffer buffer
120 (goto-char (process-mark (get-buffer-process (current-buffer))))
121 (insert (org-babel-chomp body)))
122 buffer)))
124 ;; helper functions
126 (defun org-babel-variable-assignments:julia (params)
127 "Return list of julia statements assigning the block's variables."
128 (let ((vars (mapcar #'cdr (org-babel-get-header params :var))))
129 (mapcar
130 (lambda (pair)
131 (org-babel-julia-assign-elisp
132 (car pair) (cdr pair)
133 (equal "yes" (cdr (assoc :colnames params)))
134 (equal "yes" (cdr (assoc :rownames params)))))
135 (mapcar
136 (lambda (i)
137 (cons (car (nth i vars))
138 (org-babel-reassemble-table
139 (cdr (nth i vars))
140 (cdr (nth i (cdr (assoc :colname-names params))))
141 (cdr (nth i (cdr (assoc :rowname-names params)))))))
142 (org-number-sequence 0 (1- (length vars)))))))
144 (defun org-babel-julia-quote-csv-field (s)
145 "Quote field S for export to julia."
146 (if (stringp s)
147 (concat "\"" (mapconcat 'identity (split-string s "\"") "\"\"") "\"")
148 (format "%S" s)))
150 (defun org-babel-julia-assign-elisp (name value colnames-p rownames-p)
151 "Construct julia code assigning the elisp VALUE to a variable named NAME."
152 (if (listp value)
153 (let ((max (apply #'max (mapcar #'length (org-remove-if-not
154 #'sequencep value))))
155 (min (apply #'min (mapcar #'length (org-remove-if-not
156 #'sequencep value))))
157 (transition-file (org-babel-temp-file "julia-import-")))
158 ;; ensure VALUE has an orgtbl structure (depth of at least 2)
159 (unless (listp (car value)) (setq value (list value)))
160 (with-temp-file transition-file
161 (insert
162 (orgtbl-to-csv value '(:fmt org-babel-julia-quote-csv-field))
163 "\n"))
164 (let ((file (org-babel-process-file-name transition-file 'noquote))
165 (header (if (or (eq (nth 1 value) 'hline) colnames-p)
166 "TRUE" "FALSE"))
167 (row-names (if rownames-p "1" "NULL")))
168 (if (= max min)
169 (format "%s = readcsv(\"%s\")" name file)
170 (format "%s = readcsv(\"%s\")"
171 name file))))
172 (format "%s = %s" name (org-babel-julia-quote-csv-field value))))
174 (defvar ess-ask-for-ess-directory) ; dynamically scoped
176 (defun org-babel-julia-initiate-session (session params)
177 "If there is not a current julia process then create one."
178 (unless (string= session "none")
179 (let ((session (or session "*julia*"))
180 (ess-ask-for-ess-directory
181 (and (and (boundp 'ess-ask-for-ess-directory) ess-ask-for-ess-directory)
182 (not (cdr (assoc :dir params))))))
183 (if (org-babel-comint-buffer-livep session)
184 session
185 (save-window-excursion
186 (require 'ess) (julia)
187 (rename-buffer
188 (if (bufferp session)
189 (buffer-name session)
190 (if (stringp session)
191 session
192 (buffer-name))))
193 (current-buffer))))))
195 (defun org-babel-julia-associate-session (session)
196 "Associate julia code buffer with a julia session.
197 Make SESSION be the inferior ESS process associated with the
198 current code buffer."
199 (setq ess-local-process-name
200 (process-name (get-buffer-process session)))
201 (ess-make-buffer-current))
203 (defun org-babel-julia-graphical-output-file (params)
204 "Name of file to which julia should send graphical output."
205 (and (member "graphics" (cdr (assq :result-params params)))
206 (cdr (assq :file params))))
208 (defvar org-babel-julia-eoe-indicator "print(\"org_babel_julia_eoe\")")
209 (defvar org-babel-julia-eoe-output "org_babel_julia_eoe")
211 (defvar org-babel-julia-write-object-command "writecsv(\"%s\",%s)")
213 ;; The following was a very complicated write object command
214 ;; The replacement needs to add error catching
215 ;(defvar org-babel-julia-write-object-command "{function(object,transfer.file){object;invisible(if(inherits(try({tfile<-tempfile();write.table(object,file=tfile,sep=\"\\t\",na=\"nil\",row.names=%s,col.names=%s,quote=FALSE);file.rename(tfile,transfer.file)},silent=TRUE),\"try-error\")){if(!file.exists(transfer.file))file.create(transfer.file)})}}(object=%s,transfer.file=\"%s\")")
217 (defun org-babel-julia-evaluate
218 (session body result-type result-params column-names-p row-names-p)
219 "Evaluate julia code in BODY."
220 (if session
221 (org-babel-julia-evaluate-session
222 session body result-type result-params column-names-p row-names-p)
223 (org-babel-julia-evaluate-external-process
224 body result-type result-params column-names-p row-names-p)))
226 (defun org-babel-julia-evaluate-external-process
227 (body result-type result-params column-names-p row-names-p)
228 "Evaluate BODY in external julia process.
229 If RESULT-TYPE equals 'output then return standard output as a
230 string. If RESULT-TYPE equals 'value then return the value of the
231 last statement in BODY, as elisp."
232 (case result-type
233 (value
234 (let ((tmp-file (org-babel-temp-file "julia-")))
235 (org-babel-eval org-babel-julia-command
236 (format org-babel-julia-write-object-command
237 (org-babel-process-file-name tmp-file 'noquote)
238 (format "begin\n%s\nend" body)))
239 (org-babel-julia-process-value-result
240 (org-babel-result-cond result-params
241 (with-temp-buffer
242 (insert-file-contents tmp-file)
243 (buffer-string))
244 (org-babel-import-elisp-from-file tmp-file '(4)))
245 column-names-p)))
246 (output (org-babel-eval org-babel-julia-command body))))
248 (defun org-babel-julia-evaluate-session
249 (session body result-type result-params column-names-p row-names-p)
250 "Evaluate BODY in SESSION.
251 If RESULT-TYPE equals 'output then return standard output as a
252 string. If RESULT-TYPE equals 'value then return the value of the
253 last statement in BODY, as elisp."
254 (case result-type
255 (value
256 (with-temp-buffer
257 (insert (org-babel-chomp body))
258 (let ((ess-local-process-name
259 (process-name (get-buffer-process session)))
260 (ess-eval-visibly-p nil))
261 (ess-eval-buffer nil)))
262 (let ((tmp-file (org-babel-temp-file "julia-")))
263 (org-babel-comint-eval-invisibly-and-wait-for-file
264 session tmp-file
265 (format org-babel-julia-write-object-command
266 (org-babel-process-file-name tmp-file 'noquote) "ans"))
267 (org-babel-julia-process-value-result
268 (org-babel-result-cond result-params
269 (with-temp-buffer
270 (insert-file-contents tmp-file)
271 (buffer-string))
272 (org-babel-import-elisp-from-file tmp-file '(4)))
273 column-names-p)))
274 (output
275 (mapconcat
276 #'org-babel-chomp
277 (butlast
278 (delq nil
279 (mapcar
280 (lambda (line) (when (> (length line) 0) line))
281 (mapcar
282 (lambda (line) ;; cleanup extra prompts left in output
283 (if (string-match
284 "^\\([ ]*[>+\\.][ ]?\\)+\\([[0-9]+\\|[ ]\\)" line)
285 (substring line (match-end 1))
286 line))
287 (org-babel-comint-with-output (session org-babel-julia-eoe-output)
288 (insert (mapconcat #'org-babel-chomp
289 (list body org-babel-julia-eoe-indicator)
290 "\n"))
291 (inferior-ess-send-input)))))) "\n"))))
293 (defun org-babel-julia-process-value-result (result column-names-p)
294 "julia-specific processing of return value.
295 Insert hline if column names in output have been requested."
296 (if column-names-p
297 (cons (car result) (cons 'hline (cdr result)))
298 result))
300 (provide 'ob-julia)
302 ;;; ob-julia.el ends here