ob-oz.el 12 KB

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