emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
blob deaa434f8f36e377fd58d7cda8464a2cd0826a3a 15651 bytes (raw)
name: lisp/ob-haskell.el 	 # note: path name is non-authoritative(*)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
 
;;; ob-haskell.el --- Babel Functions for Haskell    -*- lexical-binding: t; -*-

;; Copyright (C) 2009-2023 Free Software Foundation, Inc.

;; Author: Eric Schulte
;; Maintainer: Lawrence Bottorff <borgauf@gmail.com>
;; Keywords: literate programming, reproducible research
;; URL: https://orgmode.org

;; This file is part of GNU Emacs.

;; GNU Emacs 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 of the License, or
;; (at your option) any later version.

;; GNU Emacs 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.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Org Babel support for evaluating Haskell source code.
;; Haskell programs must be compiled before
;; they can be run, but haskell code can also be run through an
;; interactive interpreter.
;;
;; By default we evaluate using the Haskell interpreter.
;; To use the compiler, specify :compile yes in the header.

;;; Requirements:

;; - haskell-mode: https://www.iro.umontreal.ca/~monnier/elisp/#haskell-mode
;; - inf-haskell: https://www.iro.umontreal.ca/~monnier/elisp/#haskell-mode
;; - (optionally) lhs2tex: https://people.cs.uu.nl/andres/lhs2tex/

;;; Code:

(require 'org-macs)
(org-assert-version)

(require 'ob)
(require 'org-macs)
(require 'comint)

(declare-function haskell-mode "ext:haskell-mode" ())
(declare-function run-haskell "ext:inf-haskell" (&optional arg))
(declare-function inferior-haskell-load-file
		  "ext:inf-haskell" (&optional reload))
(declare-function inferior-haskell-start-process
                  "ext:inf-haskell" ())
(declare-function org-entry-get "org" (pom property &optional inherit literal-nil))

(defvar org-babel-tangle-lang-exts)
(add-to-list 'org-babel-tangle-lang-exts '("haskell" . "hs"))

(defvar org-babel-default-header-args:haskell
  '((:padlines . "no")))

(defvar org-babel-haskell-lhs2tex-command "lhs2tex")

(defvar org-babel-haskell-eoe "org-babel-haskell-eoe")

(defvar haskell-prompt-regexp)

(defcustom org-babel-haskell-compiler "ghc"
  "Command used to compile a Haskell source code file into an executable.
May be either a command in the path, like \"ghc\" or an absolute
path name, like \"/usr/local/bin/ghc\".  The command can include
a parameter, such as \"ghc -v\"."
  :group 'org-babel
  :package-version '(Org "9.4")
  :type 'string)

(defconst org-babel-header-args:haskell '((compile . :any))
  "Haskell-specific header arguments.")

(defun org-babel-haskell-execute (body params)
  "This function should only be called by `org-babel-execute:haskell'."
  (let* ((tmp-src-file (org-babel-temp-file "Haskell-src-" ".hs"))
         (tmp-bin-file
          (org-babel-process-file-name
           (org-babel-temp-file "Haskell-bin-" org-babel-exeext)))
         (cmdline (cdr (assq :cmdline params)))
         (cmdline (if cmdline (concat " " cmdline) ""))
         (flags (cdr (assq :flags params)))
         (flags (mapconcat #'identity
		           (if (listp flags)
                               flags
                             (list flags))
			   " "))
         (libs (org-babel-read
	        (or (cdr (assq :libs params))
	            (org-entry-get nil "libs" t))
	        nil))
         (libs (mapconcat #'identity
		          (if (listp libs) libs (list libs))
		          " ")))
    (with-temp-file tmp-src-file (insert body))
    (org-babel-eval
     (format "%s -o %s %s %s %s"
             org-babel-haskell-compiler
	     tmp-bin-file
	     flags
	     (org-babel-process-file-name tmp-src-file)
	     libs)
     "")
    (let ((results (org-babel-eval (concat tmp-bin-file cmdline) "")))
      (when results
        (setq results (org-trim (org-remove-indentation results)))
        (org-babel-reassemble-table
         (org-babel-result-cond (cdr (assq :result-params params))
	   (org-babel-read results t)
	   (let ((tmp-file (org-babel-temp-file "Haskell-")))
	     (with-temp-file tmp-file (insert results))
	     (org-babel-import-elisp-from-file tmp-file)))
         (org-babel-pick-name
	  (cdr (assq :colname-names params)) (cdr (assq :colnames params)))
         (org-babel-pick-name
	  (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))))

(defun org-babel-interpret-haskell (body params)
  (org-require-package 'inf-haskell "haskell-mode")
  (add-hook 'inferior-haskell-hook
            (lambda ()
              (setq-local comint-prompt-regexp
                          (concat haskell-prompt-regexp "\\|^λ?> "))))
  (org-babel-haskell-with-session
   params
   (lambda (session)
     (cl-labels
         ((csend (txt)
            (insert txt) (comint-send-input nil t))
          (eom ()
            (csend (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")))
          (with-output (todo)
            (let ((comint-preoutput-filter-functions
                   (cons 'ansi-color-filter-apply
                         comint-preoutput-filter-functions)))
              (org-babel-comint-with-output
                  (session org-babel-haskell-eoe nil nil)
                (funcall todo)))))
       (let* ((result-type (cdr (assq :result-type params)))
              (full-body (org-babel-expand-body:generic
                          body params
                          (org-babel-variable-assignments:haskell params)))
              (raw (pcase result-type
                     (`output
                      (with-output
                       (lambda () (csend (org-trim full-body)) (eom))))
                     (`value
                      ;; We first compute the value and store the
                      ;; value, ignoring any output.
                      (with-output
                       (lambda ()
                         (csend "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n")
                         (csend (org-trim full-body))
                         (csend "__LAST_VALUE_IMPROBABLE_NAME__=it\n")
                         (eom)))
                      ;; We now display and capture the value.
                      (with-output
                       (lambda()
                         (csend "__LAST_VALUE_IMPROBABLE_NAME__\n")
                         (eom))))))
              (results (mapcar #'org-strip-quotes
                               (cdr (member org-babel-haskell-eoe
                                            (reverse (mapcar #'org-trim raw)))))))
         (org-babel-reassemble-table
          (let ((result
                 (pcase result-type
                   (`output (mapconcat #'identity (reverse results) "\n"))
                   (`value (car results)))))
            (org-babel-result-cond (cdr (assq :result-params params))
	      result (when result (org-babel-script-escape result))))
          (org-babel-pick-name (cdr (assq :colname-names params))
			       (cdr (assq :colname-names params)))
          (org-babel-pick-name (cdr (assq :rowname-names params))
			       (cdr (assq :rowname-names params)))))))))


(defun org-babel-execute:haskell (body params)
  "Execute a block of Haskell code."
  (let ((compile (string= "yes" (cdr (assq :compile params)))))
    (if (not compile)
	(org-babel-interpret-haskell body params)
      (org-babel-haskell-execute body params))))


(defun org-babel-haskell-with-session (params todo)
  "Call TODO with a suitable session buffer.
Use PARAMS to get/create/destroy the session as needed.
Return the result of the call."
  (let* ((sn (cdr (assq :session params)))
         (session (org-babel-haskell-initiate-session sn params))
         (one-shot (equal sn "none")))
    (unwind-protect
        (funcall todo session)
      (when (and one-shot (buffer-live-p session))
        ;; As we don't control how the session temporary buffer is
        ;; created, we need to explicitly work around the hooks and
        ;; query functions.
        (with-current-buffer session
          (let ((kill-buffer-query-functions nil)
                (kill-buffer-hook nil))
            (kill-buffer session)))))))


;; Variable defined in inf-haskell (haskell-mode package).
(defvar inferior-haskell-buffer)

(defun org-babel-haskell-initiate-session (&optional session-name _params)
  "Initiate a haskell session.
Return the initialized session, i.e. the buffer for this session.
When SESSION-NAME is nil, use a global session named
\"*ob-haskell*\".  When SESSION-NAME is the string \"none\", use
a temporary buffer.  Else, (re)use the session named
SESSION-NAME.  The buffer name is the session name.  See also
`org-babel-haskell-with-session'."
  (org-require-package 'inf-haskell "haskell-mode")
  (cond
   ((equal "none" session-name)
    ;; Temporary buffer name.
    (setq session-name (generate-new-buffer-name " *ob-haskell-tmp*")))
   ((eq nil session-name)
    ;; The global default session. As haskell-mode is using the buffer
    ;; named "*haskell*", we stay away from it.
    (setq session-name "*ob-haskell*")))
  (let ((session (get-buffer session-name)))
    (save-window-excursion
      (or (org-babel-comint-buffer-livep session)
          (let ((inferior-haskell-buffer session))
            ;; As inferior-haskell expects the buffer to be named
            ;; "*haskell*", we rename it, unless the user explicitly
            ;; requested to use the name "*haskell*".
            (when (not (equal "*haskell*" session-name))
              (when (and (bufferp session)
                         (not (org-babel-comint-buffer-livep session)))
                (when (bufferp "*haskell*")
                  (user-error "Conflicting buffer '*haskell*', rename it or kill it"))
                (with-current-buffer session (rename-buffer "*haskell*"))))
            (unwind-protect
                (save-window-excursion
                  ;; We don't use `run-haskell' to not popup the buffer.
                  ;; And we protect default-directory.
                  (let ((default-directory default-directory))
                    (inferior-haskell-start-process))
                  (sleep-for 0.25)
                  (setq session inferior-haskell-buffer))
              (when (and (not (equal "*haskell*" session-name))
                         (bufferp session))
                (with-current-buffer session (rename-buffer session-name))))
            ;; Disable secondary prompt.
            (org-babel-comint-input-command
             session
             ":set prompt-cont \"\"")
            session)
          ))
    session))


(defun org-babel-load-session:haskell (session body params)
  "Load BODY into SESSION."
  (save-window-excursion
    (let* ((buffer (org-babel-prep-session:haskell session params))
           (load-file (concat (org-babel-temp-file "haskell-load-") ".hs")))
      (with-temp-buffer
        (insert body) (write-file load-file)
        (haskell-mode) (inferior-haskell-load-file))
      buffer)))

(defun org-babel-prep-session:haskell (session params)
  "Prepare SESSION according to the header arguments in PARAMS."
  (save-window-excursion
    (let ((buffer (org-babel-haskell-initiate-session session params)))
      (org-babel-comint-in-buffer buffer
      	(mapc (lambda (line)
		(insert line)
		(comint-send-input nil t))
	      (org-babel-variable-assignments:haskell params)))
      (current-buffer))))

(defun org-babel-variable-assignments:haskell (params)
  "Return list of haskell statements assigning the block's variables."
  (mapcar (lambda (pair)
	    (format "let %s = %s"
		    (car pair)
		    (org-babel-haskell-var-to-haskell (cdr pair))))
	  (org-babel--get-vars params)))

(defun org-babel-haskell-var-to-haskell (var)
  "Convert an elisp value VAR into a haskell variable.
The elisp VAR is converted to a string of haskell source code
specifying a variable of the same value."
  (if (listp var)
      (concat "[" (mapconcat #'org-babel-haskell-var-to-haskell var ", ") "]")
    (format "%S" var)))

(defvar org-export-copy-to-kill-ring)
(declare-function org-export-to-file "ox"
		  (backend file
			   &optional async subtreep visible-only body-only
			   ext-plist post-process))
(defun org-babel-haskell-export-to-lhs (&optional arg)
  "Export to a .lhs file with all haskell code blocks escaped.
When called with a prefix argument the resulting
.lhs file will be exported to a .tex file.  This function will
create two new files, base-name.lhs and base-name.tex where
base-name is the name of the current Org file.

Note that all standard Babel literate programming
constructs (header arguments, no-web syntax etc...) are ignored."
  (interactive "P")
  (let* ((contents (buffer-string))
         (haskell-regexp
          (concat "^\\([ \t]*\\)#\\+begin_src[ \t]haskell*\\(.*\\)[\r\n]"
                  "\\(\\(?:.\\|\n\\)*?\\)[\r\n][ \t]*#\\+end_src.*"))
         (base-name (file-name-sans-extension (buffer-file-name)))
         (tmp-file (org-babel-temp-file "haskell-"))
         (tmp-org-file (concat tmp-file ".org"))
         (tmp-tex-file (concat tmp-file ".tex"))
         (lhs-file (concat base-name ".lhs"))
         (tex-file (concat base-name ".tex"))
         (command (concat org-babel-haskell-lhs2tex-command
			  " " (org-babel-process-file-name lhs-file)
			  " > " (org-babel-process-file-name tex-file)))
         (preserve-indentp org-src-preserve-indentation)
         indentation)
    ;; escape haskell source-code blocks
    (with-temp-file tmp-org-file
      (insert contents)
      (goto-char (point-min))
      (while (re-search-forward haskell-regexp nil t)
        (save-match-data (setq indentation (length (match-string 1))))
        (replace-match (save-match-data
                         (concat
                          "#+begin_export latex\n\\begin{code}\n"
                          (if (or preserve-indentp
                                  (string-match "-i" (match-string 2)))
                              (match-string 3)
                            (org-remove-indentation (match-string 3)))
                          "\n\\end{code}\n#+end_export\n"))
                       t t)
        (indent-code-rigidly (match-beginning 0) (match-end 0) indentation)))
    (save-excursion
      (unwind-protect
          (with-temp-buffer
            ;; Export to latex w/org and save as .lhs
            (require 'ox-latex)
            (insert-file-contents tmp-org-file)
            ;; Ensure we do not clutter kill ring with incomplete results.
            (let (org-export-copy-to-kill-ring)
	      (org-export-to-file 'latex tmp-tex-file)))
        (delete-file tmp-org-file))
      (unwind-protect
          (with-temp-buffer
            (insert-file-contents tmp-tex-file)
            (goto-char (point-min)) (forward-line 2)
            (insert "%include polycode.fmt\n")
            ;; ensure all \begin/end{code} statements start at the first column
            (while (re-search-forward "^[ \t]+\\\\begin{code}\\(?:.\\|\n\\)+\\\\end{code}" nil t)
              (replace-match (save-match-data (org-remove-indentation (match-string 0)))
                             t t))
            ;; save org exported latex to a .lhs file
            (write-region nil nil lhs-file))
        (delete-file tmp-tex-file)))
    (if (not arg)
        (find-file lhs-file)
      ;; process .lhs file with lhs2tex
      (message "running %s" command) (shell-command command) (find-file tex-file))))

(provide 'ob-haskell)

;;; ob-haskell.el ends here

debug log:

solving deaa434f8 ...
found deaa434f8 in https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/ ||
	https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/
found 98b1b10f0 in https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/ ||
	https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/
found 6bbc91439 in https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/ ||
	https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/
found 961ae9c8a in https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/ ||
	https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/
found 500be89a2 in https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/ ||
	https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/
found 909de19ab in https://git.savannah.gnu.org/cgit/emacs/org-mode.git
preparing index
index prepared:
100644 909de19abb5a3db5914f5f5fda159eed733e800d	lisp/ob-haskell.el

applying [1/5] https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/
diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el
index 909de19ab..500be89a2 100644

Checking patch lisp/ob-haskell.el...
Applied patch lisp/ob-haskell.el cleanly.

skipping https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/ for 500be89a2
index at:
100644 500be89a246fb103445f495855ed74c8b00d487b	lisp/ob-haskell.el

applying [2/5] https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/
diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el
index 500be89a2..961ae9c8a 100644

Checking patch lisp/ob-haskell.el...
Applied patch lisp/ob-haskell.el cleanly.

skipping https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/ for 961ae9c8a
index at:
100644 961ae9c8aed86ad1764a23a3cdbf082fbd5fe9ce	lisp/ob-haskell.el

applying [3/5] https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/
diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el
index 961ae9c8a..6bbc91439 100644

Checking patch lisp/ob-haskell.el...
Applied patch lisp/ob-haskell.el cleanly.

skipping https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/ for 6bbc91439
index at:
100644 6bbc91439be9ab2c6cd825a2dbc26cb2975281b3	lisp/ob-haskell.el

applying [4/5] https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/
diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el
index 6bbc91439..98b1b10f0 100644

Checking patch lisp/ob-haskell.el...
Applied patch lisp/ob-haskell.el cleanly.

skipping https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/ for 98b1b10f0
index at:
100644 98b1b10f05a14ad5c61146fce3422f0ba4e0a8f0	lisp/ob-haskell.el

applying [5/5] https://yhetil.org/orgmode/6457883b.5d0a0220.2b9fc.8fd2@mx.google.com/
diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el
index 98b1b10f0..deaa434f8 100644

Checking patch lisp/ob-haskell.el...
Applied patch lisp/ob-haskell.el cleanly.

skipping https://yhetil.org/orgmode/6457663a.df0a0220.f6b38.a05e@mx.google.com/ for deaa434f8
index at:
100644 deaa434f8f36e377fd58d7cda8464a2cd0826a3a	lisp/ob-haskell.el

(*) Git path names are given by the tree(s) the blob belongs to.
    Blobs themselves have no identifier aside from the hash of its contents.^

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).