ob-oz.el (12371B)
1 ;;; ob-oz.el --- Org-babel functions for Oz evaluation 2 3 ;; Copyright (C) 2009-2014, 2021 Torsten Anders and Eric Schulte 4 5 ;; Author: Torsten Anders and Eric Schulte 6 ;; Keywords: literate programming, reproducible research 7 ;; Homepage: https://git.sr.ht/~bzg/org-contrib 8 ;; Version: 0.02 9 10 ;; This file is not part of GNU Emacs. 11 12 ;; This program is free software; you can redistribute it and/or modify 13 ;; it under the terms of the GNU General Public License as published by 14 ;; the Free Software Foundation; either version 3, or (at your option) 15 ;; any later version. 16 ;; 17 ;; This program is distributed in the hope that it will be useful, 18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 ;; GNU General Public License for more details. 21 ;; 22 ;; You should have received a copy of the GNU General Public License 23 ;; along with GNU Emacs; see the file COPYING. If not, write to the 24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 25 ;; Boston, MA 02110-1301, USA. 26 27 ;;; Commentary: 28 29 ;; Org-Babel support for evaluating Oz source code. 30 ;; 31 ;; Oz code is always send to the Oz Programming Environment (OPI), the 32 ;; Emacs mode and compiler interface for Oz programs. Therefore, only 33 ;; session mode is supported. In practice, non-session code blocks are 34 ;; handled equally well by the session mode. However, only a single 35 ;; session is supported. Consequently, the :session header argument is 36 ;; ignored. 37 ;; 38 ;; The Org-babel header argument :results is interpreted as 39 ;; follows. :results output requires the respective code block to be 40 ;; an Oz statement and :results value requires an Oz 41 ;; expression. Currently, results are only supported for expressions 42 ;; (i.e. the result of :results output is always nil). 43 ;; 44 ;; Expression evaluation happens synchronously. Therefore there is an 45 ;; additional header argument :wait-time <number>, which specifies the 46 ;; maximum time to wait for the result of a given expression. nil 47 ;; means to wait as long as it takes to get a result (potentially wait 48 ;; forever). 49 ;; 50 ;; NOTE: Currently the copyright of this file may not be in a state to 51 ;; permit inclusion as core software into Emacs or Org-mode. 52 53 ;;; Requirements: 54 55 ;; - Mozart Programming System, the implementation of the Oz 56 ;; programming language (http://www.mozart-oz.org/), which includes 57 ;; the major mode mozart for editing Oz programs. 58 ;; 59 ;; - StartOzServer.oz which is located in the contrib/scripts 60 ;; directory of the Org-mode repository 61 62 ;;; TODO: 63 64 ;; - Decide: set communication to \\switch -threadedqueries? 65 ;; 66 ;; - Only start Oz compiler when required, e.g., load Org-babel only when needed? 67 ;; 68 ;; - Avoid synchronous evaluation to avoid blocking Emacs (complex 69 ;; Strasheela programs can take long to find a result..). In order 70 ;; to cleanly map code blocks to their associated results (which can 71 ;; arrive then in any order) I could use IDs 72 ;; (e.g. integers). However, how do I do concurrency in Emacs Lisp, 73 ;; and how can I define org-babel-execute:oz concurrently. 74 ;; 75 ;; - Expressions are rarely used in Oz at the top-level, and using 76 ;; them in documentation and Literate Programs will cause 77 ;; confusion. Idea: hide expression from reader and instead show 78 ;; them statement (e.g., MIDI output statement) and then include 79 ;; result in Org file. Implementation: for expressions (:results 80 ;; value) support an additional header argument that takes arbitrary 81 ;; Oz code. This code is not seen by the reader, but will be used 82 ;; for the actual expression at the end. Alternative: feed all 83 ;; relevant code as statement (:results output), then add expression 84 ;; as extra code block which outputs, e.g., file name (so the file 85 ;; name must be accessible by global var), but the code of this 86 ;; extra codeblock is not seen. Hm, in that case it might be even 87 ;; more easy to manually add this link to the Org file. 88 ;; 89 90 91 (require 'ob) 92 ;;; major mode for editing Oz programs 93 (require 'mozart nil t) 94 95 ;; 96 ;; Interface to communicate with Oz. 97 ;; (1) For statements without any results: oz-send-string 98 ;; (2) For expressions with a single result: oz-send-string-expression 99 ;; (defined in org-babel-oz-ResultsValue.el) 100 ;; 101 102 ;; oz-send-string-expression implements an additional very direct 103 ;; communication between Org-babel and the Oz compiler. Communication 104 ;; with the Oz server works already without this code via the function 105 ;; oz-send-string from mozart.el.in, but this function does not get 106 ;; back any results from Oz to Emacs. The following code creates a 107 ;; socket for sending code to the OPI compiler and results are 108 ;; returned by the same socket. On the Oz side, a socket is opened and 109 ;; connected to the compiler of the OPI (via oz-send-string). On the 110 ;; Emacs side, a connection to this socket is created for feeding code 111 ;; and receiving results. This additional communication channel to the 112 ;; OPI compiler ensures that results are returned cleanly (e.g., only 113 ;; the result of the sent code is returned, no parsing or any 114 ;; processing of *Oz Emulator* is required). 115 ;; 116 ;; There is no buffer, nor sentinel involved. Oz code is send 117 ;; directly, and results from Oz are send back, but Emacs Lisp 118 ;; requires a filter function for processing results. 119 120 (defvar org-babel-oz-server-dir 121 (file-name-as-directory 122 (expand-file-name 123 "contrib/scripts" 124 (file-name-as-directory 125 (expand-file-name 126 "../../.." 127 (file-name-directory (or load-file-name buffer-file-name)))))) 128 "Path to the contrib/scripts directory in which 129 StartOzServer.oz is located.") 130 131 (defvar org-babel-oz-port 6001 132 "Port for communicating with Oz compiler.") 133 (defvar org-babel-oz-OPI-socket nil 134 "Socket for communicating with OPI.") 135 136 (defvar org-babel-oz-collected-result nil 137 "Aux var to hand result from org-babel-oz-filter to oz-send-string-expression.") 138 (defun org-babel-oz-filter (proc string) 139 "Processes output from socket org-babel-oz-OPI-socket." 140 ;; (setq org-babel-oz-collected-results (cons string org-babel-oz-collected-results)) 141 (setq org-babel-oz-collected-result string) 142 ) 143 144 145 (defun org-babel-oz-create-socket () 146 (message "Create OPI socket for evaluating expressions") 147 ;; Start Oz directly 148 (run-oz) 149 ;; Create socket on Oz side (after Oz was started). 150 (oz-send-string (concat "\\insert '" org-babel-oz-server-dir "StartOzServer.oz'")) 151 ;; Wait until socket is created before connecting to it. 152 ;; Quick hack: wait 3 sec 153 ;; 154 ;; extending time to 30 secs does not help when starting Emacs for 155 ;; the first time (and computer does nothing else) 156 (sit-for 3) 157 ;; connect to OPI socket 158 (setq org-babel-oz-OPI-socket 159 ;; Creates a socket. I/O interface of Emacs sockets as for processes. 160 (open-network-stream "*Org-babel-OPI-socket*" nil "localhost" org-babel-oz-port)) 161 ;; install filter 162 (set-process-filter org-babel-oz-OPI-socket #'org-babel-oz-filter) 163 ) 164 165 ;; communication with org-babel-oz-OPI-socket is asynchronous, but 166 ;; oz-send-string-expression turns is into synchronous... 167 (defun oz-send-string-expression (string &optional wait-time) 168 "Similar to oz-send-string, oz-send-string-expression sends a string to the OPI compiler. However, string must be expression and this function returns the result of the expression (as string). oz-send-string-expression is synchronous, wait-time allows to specify a maximum wait time. After wait-time is over with no result, the function returns nil." 169 (if (not org-babel-oz-OPI-socket) 170 (org-babel-oz-create-socket)) 171 (let ((polling-delay 0.1) 172 result) 173 (process-send-string org-babel-oz-OPI-socket string) 174 ;; wait for result 175 (if wait-time 176 (let ((waited 0)) 177 (unwind-protect 178 (progn 179 (while 180 ;; stop loop if org-babel-oz-collected-result \= nil or waiting time is over 181 (not (or (not (equal org-babel-oz-collected-result nil)) 182 (> waited wait-time))) 183 (progn 184 (sit-for polling-delay) 185 ;; (message "org-babel-oz: next polling iteration") 186 (setq waited (+ waited polling-delay)))) 187 ;; (message "org-babel-oz: waiting over, got result or waiting timed out") 188 ;; (message (format "wait-time: %s, waited: %s" wait-time waited)) 189 (setq result org-babel-oz-collected-result) 190 (setq org-babel-oz-collected-result nil)))) 191 (unwind-protect 192 (progn 193 (while (equal org-babel-oz-collected-result nil) 194 (sit-for polling-delay)) 195 (setq result org-babel-oz-collected-result) 196 (setq org-babel-oz-collected-result nil)))) 197 result)) 198 199 (defun org-babel-expand-body:oz (body params) 200 (let ((vars (org-babel--get-vars params))) 201 (if vars 202 ;; prepend code to define all arguments passed to the code block 203 (let ((var-string (mapcar (lambda (pair) 204 (format "%s=%s" 205 (car pair) 206 (org-babel-oz-var-to-oz (cdr pair)))) 207 vars))) 208 ;; only add var declarations if any variables are there 209 (mapconcat #'identity 210 (append (list "local") var-string (list "in" body "end")) 211 "\n")) 212 body))) 213 214 (defun org-babel-execute:oz (body params) 215 "Execute a block of Oz code with org-babel. This function is 216 called by `org-babel-execute-src-block' via multiple-value-bind." 217 (let* ((result-params (cdr (assq :result-params params))) 218 (full-body (org-babel-expand-body:oz body params)) 219 (wait-time (plist-get params :wait-time))) 220 ;; actually execute the source-code block 221 (org-babel-reassemble-table 222 (cond 223 ((member "output" result-params) 224 (message "Org-babel: executing Oz statement") 225 (oz-send-string full-body)) 226 ((member "value" result-params) 227 (message "Org-babel: executing Oz expression") 228 (oz-send-string-expression full-body (or wait-time 1))) 229 (t (error "either 'output' or 'results' must be members of :results"))) 230 (org-babel-pick-name (cdr (assq :colname-names params)) 231 (cdr (assq :colnames params))) 232 (org-babel-pick-name (cdr (assq :roname-names params)) 233 (cdr (assq :rownames params)))))) 234 235 ;; This function should be used to assign any variables in params in 236 ;; the context of the session environment. 237 (defun org-babel-prep-session:oz (session params) 238 "Prepare SESSION according to the header arguments specified in PARAMS." 239 (error "org-babel-prep-session:oz unimplemented")) 240 ;; TODO: testing... (copied from org-babel-haskell.el) 241 ;; (defun org-babel-prep-session:oz (session params) 242 ;; "Prepare SESSION according to the header arguments specified in PARAMS." 243 ;; (save-window-excursion 244 ;; (org-babel-oz-initiate-session session) 245 ;; (let* ((vars (org-babel-ref-variables params)) 246 ;; (var-lines (mapconcat ;; define any variables 247 ;; (lambda (pair) 248 ;; (format "%s=%s" 249 ;; (car pair) 250 ;; (org-babel-ruby-var-to-ruby (cdr pair)))) 251 ;; vars "\n")) 252 ;; (vars-file (concat (make-temp-file "org-babel-oz-vars") ".oz"))) 253 ;; (when vars 254 ;; (with-temp-buffer 255 ;; (insert var-lines) (write-file vars-file) 256 ;; (oz-mode) 257 ;; ;; (inferior-oz-load-file) ; ?? 258 ;; )) 259 ;; (current-buffer)))) 260 ;; 261 262 263 ;; TODO: testing... (simplified version of def in org-babel-prep-session:ocaml) 264 ;; 265 ;; BUG: does not work yet. Error: ad-Orig-error: buffer none doesn't exist or has no process 266 ;; UNUSED DEF 267 (defun org-babel-oz-initiate-session (&optional session params) 268 "If there is not a current inferior-process-buffer in SESSION 269 then create. Return the initialized session." 270 (unless (string= session "none") 271 ;; TODO: make it possible to have multiple sessions 272 (save-window-excursion 273 ;; (run-oz) 274 (get-buffer oz-compiler-buffer)))) 275 276 (defun org-babel-oz-var-to-oz (var) 277 "Convert an elisp var into a string of Oz source code 278 specifying a var of the same value." 279 (if (listp var) 280 ;; (concat "[" (mapconcat #'org-babel-oz-var-to-oz var ", ") "]") 281 (eval var) 282 (format "%s" var) ; don't preserve string quotes. 283 ;; (format "%s" var) 284 )) 285 286 ;; TODO: 287 (defun org-babel-oz-table-or-string (results) 288 "If the results look like a table, then convert them into an 289 Emacs-lisp table, otherwise return the results as a string." 290 (error "org-babel-oz-table-or-string unimplemented")) 291 292 293 (provide 'ob-oz) 294 ;;; org-babel-oz.el ends here