summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Schulte <schulte.eric@gmail.com>2010-02-16 13:28:08 -0700
committerEric Schulte <schulte.eric@gmail.com>2010-02-16 13:28:08 -0700
commit574f1b590e7d73ae708b4aea7d925c5669b567b2 (patch)
tree9de3c90c96508694d17980798ccfe6439ab39ace
parentca19fab38033afface85b7b7b9dd4941abbfb1d8 (diff)
downloadorg-mode-574f1b590e7d73ae708b4aea7d925c5669b567b2.tar.gz
babel: adding support for the OZ programming language -- Thanks to Torsten Anders!
For information on the OZ programming language see http://www.mozart-oz.org/, for information on using Org-babel with OZ see the OZ specific documentation on Worg at http://orgmode.org/worg/org-contrib/babel/languages/org-babel-doc-oz.php
-rw-r--r--contrib/babel/lisp/langs/org-babel-oz.el309
-rw-r--r--contrib/scripts/StartOzServer.oz220
2 files changed, 529 insertions, 0 deletions
diff --git a/contrib/babel/lisp/langs/org-babel-oz.el b/contrib/babel/lisp/langs/org-babel-oz.el
new file mode 100644
index 0000000..31b3bff
--- /dev/null
+++ b/contrib/babel/lisp/langs/org-babel-oz.el
@@ -0,0 +1,309 @@
+;;; org-babel-oz.el --- org-babel functions for Oz evaluation
+
+;; Copyright (C) 2009 Torsten Anders and Eric Schulte
+
+;; Author: Torsten Anders and Eric Schulte
+;; Keywords: literate programming, reproducible research
+;; Homepage: http://orgmode.org
+;; Version: 0.01
+
+;;; License:
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+;;
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+;;
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
+
+;;; Commentary:
+
+;; Org-Babel support for evaluating Oz source code.
+;;
+;; Oz code is always send to the Oz Programming Environment (OPI), the
+;; Emacs mode and compiler interface for Oz programs. Therefore, only
+;; session mode is supported. In practice, non-session code blocks are
+;; handled equally well by the session mode. However, only a single
+;; session is supported. Consequently, the :session header argument is
+;; ignored.
+;;
+;; The Org-babel header argument :results is interpreted as
+;; follows. :results output requires the respective code block to be
+;; an Oz statement and :results value requires an Oz
+;; expression. Currently, results are only supported for expressions
+;; (i.e. the result of :results output is always nil).
+;;
+;; Expression evaluation happens synchronously. Therefore there is an
+;; additional header argument :wait-time <number>, which specifies the
+;; maximum time to wait for the result of a given expression. nil
+;; means to wait as long as it takes to get a result (potentially wait
+;; forever).
+;;
+;; NOTE: Currently the copyright of this file may not be in a state to
+;; permit inclusion as core software into Emacs or Org-mode.
+
+;;; Requirements:
+
+;; - Mozart Programming System, the implementation of the Oz
+;; programming language (http://www.mozart-oz.org/), which includes
+;; the major mode mozart for editing Oz programs.
+;;
+;; - StartOzServer.oz which is located in the contrib/scripts
+;; directory of the Org-mode repository
+
+;;; TODO:
+
+;; - Decide: set communication to \\switch -threadedqueries?
+;;
+;; - Only start Oz compiler when required, e.g., load Org-babel only when needed?
+;;
+;; - Avoid synchronous evaluation to avoid blocking Emacs (complex
+;; Strasheela programs can take long to find a result..). In order
+;; to cleanly map code blocks to their associated results (which can
+;; arrive then in any order) I could use IDs
+;; (e.g. integers). However, how do I do concurrency in Emacs Lisp,
+;; and how can I define org-babel-execute:oz concurrently.
+;;
+;; - Expressions are rarely used in Oz at the top-level, and using
+;; them in documentation and Literate Programs will cause
+;; confusion. Idea: hide expression from reader and instead show
+;; them statement (e.g., MIDI output statement) and then include
+;; result in Org file. Implementation: for expressions (:results
+;; value) support an additional header argument that takes arbitrary
+;; Oz code. This code is not seen by the reader, but will be used
+;; for the actual expression at the end. Alternative: feed all
+;; relevant code as statement (:results output), then add expression
+;; as extra code block which outputs, e.g., file name (so the file
+;; name must be accessible by global var), but the code of this
+;; extra codeblock is not seen. Hm, in that case it might be even
+;; more easy to manually add this link to the Org file.
+;;
+
+
+(require 'org-babel)
+;;; major mode for editing Oz programs
+(require 'mozart)
+
+;; Add Oz to the list of supported languages. Org-babel will match
+;; the string below against the declared language of the source-code
+;; block.
+(org-babel-add-interpreter "oz")
+
+;; specify the name and file extension for Oz
+(add-to-list 'org-babel-tangle-langs '("oz" "oz" nil nil))
+
+;;
+;; Interface to communicate with Oz.
+;; (1) For statements without any results: oz-send-string
+;; (2) For expressions with a single result: oz-send-string-expression
+;; (defined in org-babel-oz-ResultsValue.el)
+;;
+
+;; oz-send-string-expression implements an additional very direct
+;; communication between Org-babel and the Oz compiler. Communication
+;; with the Oz server works already without this code via the function
+;; oz-send-string from mozart.el.in, but this function does not get
+;; back any results from Oz to Emacs. The following code creates a
+;; socket for sending code to the OPI compiler and results are
+;; returned by the same socket. On the Oz side, a socket is opened and
+;; conected to the compiler of the OPI (via oz-send-string). On the
+;; Emacs side, a connection to this socket is created for feeding code
+;; and receiving results. This additional communication channel to the
+;; OPI compiler ensures that results are returned cleanly (e.g., only
+;; the result of the sent code is returned, no parsing or any
+;; processing of *Oz Emulator* is required).
+;;
+;; There is no buffer, nor sentinel involved. Oz code is send
+;; directly, and results from Oz are send back, but Emacs Lisp
+;; requires a filter function for processing results.
+
+(defvar org-babel-oz-server-dir
+ (file-name-as-directory
+ (expand-file-name
+ "scripts"
+ (file-name-as-directory
+ (expand-file-name
+ "../../.."
+ (file-name-directory (or load-file-name buffer-file-name))))))
+ "Path to the contrib/scripts directory in which
+StartOzServer.oz is located.")
+
+(defvar org-babel-oz-port 6001
+ "Port for communicating with Oz compiler.")
+(defvar org-babel-oz-OPI-socket nil
+ "Socket for communicating with OPI.")
+
+(defvar org-babel-oz-collected-result nil
+ "Aux var to hand result from org-babel-oz-filter to oz-send-string-expression.")
+(defun org-babel-oz-filter (proc string)
+ "Processes output from socket org-babel-oz-OPI-socket."
+;; (setq org-babel-oz-collected-results (cons string org-babel-oz-collected-results))
+ (setq org-babel-oz-collected-result string)
+ )
+
+
+(defun org-babel-oz-create-socket ()
+ (message "Create OPI socket for evaluating expressions")
+ ;; Start Oz directly
+ (run-oz)
+ ;; Create socket on Oz side (after Oz was started).
+ (oz-send-string (concat "\\insert '" org-babel-oz-server-dir "StartOzServer.oz'"))
+ ;; Wait until socket is created before connecting to it.
+ ;; Quick hack: wait 3 sec
+ ;;
+ ;; extending time to 30 secs does not help when starting Emacs for
+ ;; the first time (and computer does nothing else)
+ (sit-for 3)
+ ;; connect to OPI socket
+ (setq org-babel-oz-OPI-socket
+ ;; Creates a socket. I/O interface of Emacs sockets as for processes.
+ (open-network-stream "*Org-babel-OPI-socket*" nil "localhost" org-babel-oz-port))
+ ;; install filter
+ (set-process-filter org-babel-oz-OPI-socket #'org-babel-oz-filter)
+)
+
+;; communication with org-babel-oz-OPI-socket is asynchronous, but
+;; oz-send-string-expression turns is into synchronous...
+(defun oz-send-string-expression (string &optional wait-time)
+ "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."
+ (if (not org-babel-oz-OPI-socket)
+ (org-babel-oz-create-socket))
+ (let ((polling-delay 0.1)
+ result)
+ (process-send-string org-babel-oz-OPI-socket string)
+ ;; wait for result
+ (if wait-time
+ (let ((waited 0))
+ (unwind-protect
+ (progn
+ (while
+ ;; stop loop if org-babel-oz-collected-result \= nil or waiting time is over
+ (not (or (not (equal org-babel-oz-collected-result nil))
+ (> waited wait-time)))
+ (progn
+ (sit-for polling-delay)
+;; (message "org-babel-oz: next polling iteration")
+ (setq waited (+ waited polling-delay))))
+;; (message "org-babel-oz: waiting over, got result or waiting timed out")
+;; (message (format "wait-time: %s, waited: %s" wait-time waited))
+ (setq result org-babel-oz-collected-result)
+ (setq org-babel-oz-collected-result nil))))
+ (unwind-protect
+ (progn
+ (while (equal org-babel-oz-collected-result nil)
+ (sit-for polling-delay))
+ (setq result org-babel-oz-collected-result)
+ (setq org-babel-oz-collected-result nil))))
+ result))
+
+
+(defun org-babel-execute:oz (body params)
+ "Execute a block of Oz code with org-babel. This function is
+called by `org-babel-execute-src-block' via multiple-value-bind."
+ (let* ((processed-params (org-babel-process-params params))
+;; (session (org-babel-ruby-initiate-session (first processed-params)))
+ (vars (second processed-params))
+;; (result-params (third processed-params))
+ (result-type (fourth processed-params))
+ (full-body (if vars
+ ;; only add var declarations if any variables are there
+ (concat
+ ;; prepend code to define all arguments passed to the code block
+ "local\n"
+ (mapconcat
+ (lambda (pair)
+ (format "%s=%s"
+ (car pair)
+ (org-babel-oz-var-to-oz (cdr pair))))
+ vars "\n") "\n"
+ "in\n"
+ body
+ "end\n")
+ body))
+ (wait-time (plist-get params :wait-time))
+ ;; set the session if the session variable is non-nil
+;; (session-buffer (org-babel-oz-initiate-session session))
+;; (session (org-babel-prep-session:oz session params))
+ )
+ ;; actually execute the source-code block
+ (case result-type
+ (output
+ (progn
+ (message "Org-babel: executing Oz statement")
+ (oz-send-string full-body)))
+ (value
+ (progn
+ (message "Org-babel: executing Oz expression")
+ (oz-send-string-expression full-body (if wait-time
+ wait-time
+ 1)))))
+ ))
+
+;; This function should be used to assign any variables in params in
+;; the context of the session environment.
+(defun org-babel-prep-session:oz (session params)
+ "Prepare SESSION according to the header arguments specified in PARAMS."
+ (error "org-babel-prep-session:oz unimplemented"))
+;; TODO: testing... (copied from org-babel-haskell.el)
+;; (defun org-babel-prep-session:oz (session params)
+;; "Prepare SESSION according to the header arguments specified in PARAMS."
+;; (save-window-excursion
+;; (org-babel-oz-initiate-session session)
+;; (let* ((vars (org-babel-ref-variables params))
+;; (var-lines (mapconcat ;; define any variables
+;; (lambda (pair)
+;; (format "%s=%s"
+;; (car pair)
+;; (org-babel-ruby-var-to-ruby (cdr pair))))
+;; vars "\n"))
+;; (vars-file (concat (make-temp-file "org-babel-oz-vars") ".oz")))
+;; (when vars
+;; (with-temp-buffer
+;; (insert var-lines) (write-file vars-file)
+;; (oz-mode)
+;; ;; (inferior-oz-load-file) ; ??
+;; ))
+;; (current-buffer))))
+;;
+
+
+;; TODO: testing... (simplified version of def in org-babel-prep-session:ocaml)
+;;
+;; BUG: does not work yet. Error: ad-Orig-error: buffer none doesn't exist or has no process
+;; UNUSED DEF
+(defun org-babel-oz-initiate-session (&optional session)
+ "If there is not a current inferior-process-buffer in SESSION
+then create. Return the initialized session."
+ (unless (string= session "none")
+ ;; TODO: make it possible to have multiple sessions
+ (save-window-excursion
+ ;; (run-oz)
+ (get-buffer oz-compiler-buffer))))
+
+(defun org-babel-oz-var-to-oz (var)
+ "Convert an elisp var into a string of Oz source code
+specifying a var of the same value."
+ (if (listp var)
+;; (concat "[" (mapconcat #'org-babel-oz-var-to-oz var ", ") "]")
+ (eval var)
+ (format "%s" var) ; don't preserve string quotes.
+;; (format "%s" var)
+ ))
+
+;; TODO:
+(defun org-babel-oz-table-or-string (results)
+ "If the results look like a table, then convert them into an
+Emacs-lisp table, otherwise return the results as a string."
+ (error "org-babel-oz-table-or-string unimplemented"))
+
+
+(provide 'org-babel-oz)
+;;; org-babel-oz.el ends here
diff --git a/contrib/scripts/StartOzServer.oz b/contrib/scripts/StartOzServer.oz
new file mode 100644
index 0000000..4d5144e
--- /dev/null
+++ b/contrib/scripts/StartOzServer.oz
@@ -0,0 +1,220 @@
+%%% *************************************************************
+%%% Copyright (C) 2009 Torsten Anders (www.torsten-anders.de)
+%%% This program is free software; you can redistribute it and/or
+%%% modify it under the terms of the GNU General Public License
+%%% as published by the Free Software Foundation; either version 2
+%%% of the License, or (at your option) any later version.
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%% *************************************************************
+
+%%
+%% This code implements the Oz-side of the Org-babel Oz interface. It
+%% creates a socket server (to which org-babel-oz.el then
+%% connects). Any input to this socket must be an Oz expression. The
+%% input is fed to the OPI oz compiler, and the results are send back
+%% via the socket.
+%%
+
+
+declare
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Accessing the OPI compiler
+%%
+
+MyCompiler = Emacs.condSend.compiler
+
+
+/* % testing
+
+%% Feed an expression (result browsed)
+{MyCompiler enqueue(setSwitch(expression true))}
+{Browse
+ {MyCompiler enqueue(feedVirtualString("1 + 2" return(result: $)))}}
+{MyCompiler enqueue(setSwitch(expression false))}
+
+%% It is really the OPI: I can use declare!
+{MyCompiler enqueue(setSwitch(expression false))}
+{MyCompiler enqueue(feedVirtualString("declare X=3\n{Browse X*X}"))}
+
+%% Note: expressions starting with keyword declare need keyword in
+{MyCompiler enqueue(setSwitch(expression true))}
+{Browse
+ {MyCompiler enqueue(feedVirtualString("declare X=3\nin X*X" return(result: $)))}}
+{MyCompiler enqueue(setSwitch(expression false))}
+
+%% Alternatively you use a session with multiple feeds: first declare (statement), and then feed an expression
+{MyCompiler enqueue(setSwitch(expression false))}
+{MyCompiler enqueue(feedVirtualString("declare X=7" return))}
+{MyCompiler enqueue(setSwitch(expression true))}
+{Browse
+ {MyCompiler enqueue(feedVirtualString("X*X" return(result: $)))}}
+{MyCompiler enqueue(setSwitch(expression false))}
+
+%% !!?? does not work?
+%% return nil in case of any error (division by 0)
+{MyCompiler enqueue(setSwitch(expression true))}
+{Browse
+ {MyCompiler enqueue(feedVirtualString(
+ {LUtils.accum ["try\n"
+% "skip\n" % do something in any case..
+ "1 div 0" % my code
+% "1" % my code
+ "\ncatch E then {Error.printException E}\n"
+ "error\n" % always return nil
+ "end\n"]
+ List.append}
+ return(result: $)))}}
+{MyCompiler enqueue(setSwitch(expression false))}
+
+
+%% !! catching some exceptions does not work??
+
+%% exception is not catched
+try {Bla} catch E then {Error.printException E} {Browse nil} end
+
+%% exception is catched
+try {Browse 1 div 0} catch E then {Error.printException E} {Browse nil} end
+{Browse ok}
+
+
+*/
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Socket interface
+%%
+
+
+%%
+%% Create socket
+%%
+
+MyPort = 6001
+
+/** %% Creates a TCP socket server. Expects a Host (e.g., 'localhost') and a PortNo and returns a server plus its corresponding client. This client is an instance of Open.socket, and is the interface for reading and writing into the socket.
+%% MakeServer blocks until the server listens. However, waiting until a connection has been accepted happens in its own thread (i.e. MakeServer does only block until the server listens).
+%% NB: A port can be used only once, so assign it carefully. In case this postnnumber was shortly used before, you may need to wait a bit before reusing it.
+%% */
+%% !! Alternatively, let it assign automatically and output the port number..
+%%
+%% NOTE: for supporting multiple connections see http://www.mozart-oz.org/documentation/op/node13.html#section.sockets.accept
+proc {MakeServer Host PortNo ?MyServer ?MyClient}
+ proc {Accept MyClient}
+ thread H in % P
+ %% suspends until a connection has been accepted
+ {MyServer accept(host:H
+ acceptClass:Open.socket
+ accepted:?MyClient)}
+% {Myserver accept(host:H port:P)} % suspends until a connection has been accepted
+ %% !!?? port number of client is usually created randomly..
+ {System.showInfo "% connection accepted from host "#H}
+ end
+ %% !!???
+ %% If Accept is called recursively, then server accepts multiple connections. These share the same compiler instance (e.g. variable bindings are shared). For multiple independent compiler instances call the OzServer application multiple times.
+ %% However, how shall the output for multiple connections be sorted?? Would using the different client sockets created with the Server accept method work?
+ %% NB: The number of clients accepted concurrently must be limited to the number set by {MyServer listen}
+ % {Accept}
+ end
+in
+ MyServer = {New Open.socket init}
+ %% To avoid problems with portnumbers, the port could be assigned automatically and then output..
+ %%{MyServer bind(port:PortNo)}
+ {MyServer bind(host:Host takePort:PortNo)}
+ {MyServer listen}
+ {System.showInfo "% OzServer started at host "#Host#" and port "#PortNo}
+ MyClient = {Accept}
+end
+%%
+MySocket = {MakeServer localhost MyPort _/*MyServer*/}
+
+
+%%
+%% Read socket input
+%%
+
+declare
+%% Copied from OzServer/source/Socket.oz
+local
+ proc {Aux Socket Size Stream}
+ In = {Socket read(list:$
+ size:Size)}
+ in
+ {Wait In}
+ %% !! Is this the right way to stop the processing??
+ %%
+ %% abort condition when client stream ended (i.e. nothing was sent)
+ if In == nil
+ then {System.showInfo "socket stream ended"}
+ Stream = nil
+ else Stream = In | {Aux Socket Size}
+ end
+ end
+in
+ /** %% The socket Server returns a stream of the strings it receives. The Server always waits until someone writes something into the socket, then the input is immediately written to a stream and the Server waits again.
+ %% */
+ proc {ReadToStream Socket Size Xs}
+ thread {Aux Socket Size Xs} end
+ end
+end
+
+/* % test
+
+MyStream = {ReadToStream MySocket 1024}
+
+*/
+
+/* % test
+
+%% writing
+{MySocket write(vs:"this is a test")}
+
+*/
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Send socket input to compiler and send results back to socket
+%%
+
+%% NOTE: Input code must be expression
+thread
+ {ForAll {ReadToStream MySocket 1024}
+ proc {$ Code}
+ Result
+ %% Catch any exception (so the will not cause blocking) and return nil in that case
+ FullCode = {LUtils.accum ["try\n"
+% "skip\n" % do something in any case..
+ Code
+ "\ncatch E then {Error.printException E}\n"
+ "error\n" % in case of an error, return 'error'
+ "end\n"]
+ List.append}
+ in
+ %% ?? Should I make setting switches etc atomic?
+ {MyCompiler enqueue(setSwitch(expression true))}
+ Result = {MyCompiler enqueue(feedVirtualString(FullCode return(result: $)))}
+ {MyCompiler enqueue(setSwitch(expression false))}
+ %%
+ {Wait Result}
+ {MySocket write(vs: if {VirtualString.is Result}
+ then Result
+ else {Value.toVirtualString Result 1000 1000}
+ end)}
+ {Show 'Org-babel result: '#Result}
+ end}
+end
+
+
+
+
+
+
+
+
+