dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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