branch: externals/ob-haxe commit ce15a4f61e3447087e921451262a5dee8a14cd6d Author: Ian Martins <ia...@jhu.edu> Commit: Ian Martins <ia...@jhu.edu>
Add initial implementation and tests --- .gitignore | 1 + README.md | 2 - README.org | 4 + ob-haxe.el | 388 +++++++++++++++++++++++++++++++++++++++ test-ob-haxe.el | 552 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 945 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c531d98 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.elc diff --git a/README.md b/README.md deleted file mode 100644 index 45a2886..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# ob-haxe.el -org babel functions for haxe evaluation diff --git a/README.org b/README.org new file mode 100644 index 0000000..a8b53f5 --- /dev/null +++ b/README.org @@ -0,0 +1,4 @@ +* ob-haxe.el +Org babel functions for haxe evaluation. + +Full documentation will be available on [[https://orgmode.org/worg/org-contrib/babel/languages/index.html][worg]] (when I write it). diff --git a/ob-haxe.el b/ob-haxe.el new file mode 100644 index 0000000..f2e463f --- /dev/null +++ b/ob-haxe.el @@ -0,0 +1,388 @@ +;;; ob-haxe.el --- org-babel functions for haxe evaluation -*- lexical-binding: t -*- + +;; Copyright (C) 2011-2021 Free Software Foundation, Inc. + +;; Author: Ian Martins +;; Keywords: literate programming, reproducible research +;; Homepage: https://orgmode.org + +;; This file is not 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 haxe source code. + +;;; Code: +(require 'ob) + +(defvar org-babel-tangle-lang-exts) +(add-to-list 'org-babel-tangle-lang-exts '("haxe" . "hx")) + +(defvar org-babel-temporary-directory) ; from ob-core + +(defvar org-babel-default-header-args:haxe '() + "Default header args for haxe source blocks.") + +(defconst org-babel-header-args:haxe '((imports . :any)) + "Haxe-specific header arguments.") + +(defcustom org-babel-neko-command "neko" + "Name of the neko command. +May be either a command in the path, like neko or an absolute +path name, like /usr/local/bin/neko." + :group 'org-babel + :package-version '(Org . "9.5") + :type 'string) + +(defcustom org-babel-haxe-compiler "haxe" + "Name of the haxe compiler. +May be either a command in the path, like haxe or an absolute +path name, like /usr/local/bin/haxe. Parameters may be used, +like haxe --verbose." + :group 'org-babel + :package-version '(Org . "9.5") + :type 'string) + +(defcustom org-babel-haxe-hline-to "null" + "Replace hlines in incoming tables with this when translating to haxe." + :group 'org-babel + :package-version '(Org . "9.5") + :type 'string) + +(defcustom org-babel-haxe-null-to 'hline + "Replace `null' in haxe tables with this before returning." + :group 'org-babel + :package-version '(Org . "9.5") + :type 'symbol) + +(defconst org-babel-haxe--package-re (rx line-start (0+ space) "package" + (1+ space) (group (1+ (in alnum ?_ ?.))) ; capture the package name + (0+ space) ?\; line-end) + "Regexp for the package statement.") +(defconst org-babel-haxe--imports-re (rx line-start (0+ space) "import" + (1+ space) (group (1+ (in alnum ?_ ?.))) ; capture the fully qualified class name + (0+ space) ?\; line-end) + "Regexp for import statements.") +(defconst org-babel-haxe--class-re (rx line-start (0+ space) "class" (1+ space) + (group (1+ (in alnum ?_))) ; capture the class name + (0+ space) ?{) + "Regexp for the class declaration.") +(defconst org-babel-haxe--main-re (rx line-start (0+ space) + (opt "public" (1+ space)) + "static" + (1+ space) "function" + (1+ space) "main" + (0+ space) ?\( + (0+ space) ?\) + (0+ space) ?{) + "Regexp for the main method declaration.") +(defconst org-babel-haxe--any-method-re (rx line-start + (0+ space) (opt (seq (1+ alnum) (1+ space))) ; visibility + (opt (seq "static" (1+ space))) ; binding + "function" ; function + (1+ space) (1+ (in alnum ?_)) ; method name + (0+ space) ?\( + (0+ space) (0+ (in alnum ?_ ?\[ ?\] ?, space)) ; params + (0+ space) ?\) + (0+ space) (opt ?: (0+ space) (1+ (in alnum ?_ ?\[ ?\]))) ; return type + (0+ space) ?{) + "Regexp for any method.") +(defconst org-babel-haxe--result-wrapper " + public static function main() { + var output = File.write(\"%s\"); + output.writeString(haxe.Json.stringify(_main())); + output.close(); + }\n" + "Code to inject into a class so that we can capture the value it returns. +This implementation was inspired by ob-python, although not as +elegant. This modified the source block to write out the value +it wants to return to a temporary file so that ob-haxe can read +it back. The name of the temporary file to write must be +replaced in this string.") + +(defun org-babel-execute:haxe (body params) + "Execute a haxe source block with BODY code and PARAMS params." + (let* (;; if true, run from babel temp directory + (run-from-temp (not (alist-get :dir params))) + ;; class and package + (fullclassname (or (cdr (assq :classname params)) + (org-babel-haxe-find-classname body))) + ;; just the class name + (classname (car (last (split-string fullclassname "\\.")))) + ;; just the package name + (packagename (if (string-match-p "\\." fullclassname) + (file-name-base fullclassname))) + ;; the base dir that contains the top level package dir + (basedir (file-name-as-directory (if run-from-temp + (if (file-remote-p default-directory) + (concat + (file-remote-p default-directory) + org-babel-remote-temporary-directory) + org-babel-temporary-directory) + default-directory))) + (basedir-processed (org-babel-process-file-name basedir 'noquote)) + ;; the dir to write the source file + (packagedir (if (and (not run-from-temp) packagename) + (file-name-as-directory + (concat basedir (replace-regexp-in-string "\\\." "/" packagename))) + basedir)) + ;; runtime flags + (cmdline (or (cdr (assq :cmdline params)) "")) + ;; compilation target + (target-name (cdr (assq :target params))) + (target (pcase target-name + ("neko" + (format "-neko %smain.n -cmd \"neko %smain.n %s\"" + basedir-processed basedir-processed cmdline)) + ("hashlink" + (format "-hl %smain.hl -cmd \"hl %smain.hl %s\"" + basedir-processed basedir-processed cmdline)) + (_ + (if (> (length cmdline) 0) + (error "Cmdline args not allowed for interp target") + "--interp")))) + ;; the command to compile and run + (cmd (concat org-babel-haxe-compiler + " -p " basedir + " -main " (if run-from-temp classname fullclassname) " " target)) + ;; header args for result processing + (result-type (cdr (assq :result-type params))) + (result-params (cdr (assq :result-params params))) + (result-file (and (eq result-type 'value) + (org-babel-temp-file "haxe-"))) + ;; the expanded body of the source block + (full-body (org-babel-expand-body:haxe body params))) + + ;; created package-name directories if missing + (unless (or (not packagedir) (file-exists-p packagedir)) + (make-directory packagedir 'parents)) + + ;; write the source file + (setq full-body (org-babel-haxe--expand-for-evaluation + full-body run-from-temp result-type result-file)) + (with-temp-file (concat (file-name-as-directory packagedir) + classname ".hx") + (insert full-body)) + + ;; compile, run, process result + (org-babel-reassemble-table + (org-babel-haxe-evaluate cmd result-type result-params result-file) + (org-babel-pick-name + (cdr (assoc :colname-names params)) (cdr (assoc :colnames params))) + (org-babel-pick-name + (cdr (assoc :rowname-names params)) (cdr (assoc :rownames params)))))) + +;; helper functions + +(defun org-babel-haxe-find-classname (body) + "Try to find fully qualified class name in BODY. +Look through BODY for the package and class. If found, put them +together into a fully qualified class name and return. Else just +return class name. If that isn't found either, default to Main." + (let ((package (if (string-match org-babel-haxe--package-re body) + (match-string 1 body))) + (class (if (string-match org-babel-haxe--class-re body) + (match-string 1 body)))) + (or (and package class (concat package "." class)) + (and class class) + (and package (concat package ".Main")) + "Main"))) + +(defun org-babel-haxe--expand-for-evaluation (body suppress-package-p result-type result-file) + "Expand source block for evaluation. +In order to return a value we have to add a __toString method. +In order to prevent classes without main methods from erroring we +add a dummy main method if one is not provided. These +manipulations are done outside of `org-babel--expand-body' so +that they are hidden from tangles. + +BODY is the file content before instrumentation. + +SUPPRESS-PACKAGE-P if true, suppress the package statement. + +RESULT-TYPE is taken from params. + +RESULT-FILE is the temp file to write the result." + (with-temp-buffer + (insert body) + + ;; suppress package statement + (goto-char (point-min)) + (when (and suppress-package-p + (re-search-forward org-babel-haxe--package-re nil t)) + (replace-match "")) + + ;; add a dummy main method if needed + (goto-char (point-min)) + (when (not (re-search-forward org-babel-haxe--main-re nil t)) + (org-babel-haxe--move-past org-babel-haxe--class-re) + (insert "\n public static function main() { + Sys.print(\"success\"); + }\n\n")) + + ;; special handling to return value + (when (eq result-type 'value) + (goto-char (point-min)) + (org-babel-haxe--move-past org-babel-haxe--class-re) + (insert (format org-babel-haxe--result-wrapper + (org-babel-process-file-name result-file 'noquote))) + (search-forward "public static function main(") ; rename existing main + (replace-match "public static function _main(")) + + ;; add imports + (org-babel-haxe--import-maybe "sys.io" "File") + + (buffer-string))) + +(defun org-babel-haxe--move-past (re) + "Move point past the first occurrence of the given regexp RE." + (while (re-search-forward re nil t) + (goto-char (1+ (match-end 0))))) + +(defun org-babel-haxe--import-maybe (package class) + "Import from PACKAGE the given CLASS if it is used and not already imported." + (let (class-found import-found) + (goto-char (point-min)) + (setq class-found (re-search-forward class nil t)) + (goto-char (point-min)) + (setq import-found (re-search-forward (concat "^import .*" package ".*" class ";") nil t)) + (when (and class-found (not import-found)) + (org-babel-haxe--move-past org-babel-haxe--package-re) + (insert (concat "import " package "." class ";\n"))))) + +(defun org-babel-expand-body:haxe (body params) + "Expand BODY with PARAMS. +BODY could be a few statements, or could include a full class +definition specifying package, imports, and class. Because we +allow this flexibility in what the source block can contain, it +is simplest to expand the code block from the inside out." + (let* ((fullclassname (or (cdr (assq :classname params)) ; class and package + (org-babel-haxe-find-classname body))) + (classname (car (last (split-string fullclassname "\\.")))) ; just class name + (packagename (if (string-match-p "\\." fullclassname) ; just package name + (file-name-base fullclassname))) + (var-lines (org-babel-variable-assignments:haxe params)) + (imports-val (assq :imports params)) + (imports (if imports-val + (split-string (org-babel-read (cdr imports-val) nil) " ") + nil))) + (with-temp-buffer + (insert body) + + ;; wrap main. If there are methods defined, but no main method + ;; and no class, wrap everything in a generic main method. + (goto-char (point-min)) + (when (and (not (re-search-forward org-babel-haxe--main-re nil t)) + (not (re-search-forward org-babel-haxe--any-method-re nil t))) + (org-babel-haxe--move-past org-babel-haxe--package-re) ; if package is defined, move past it + (org-babel-haxe--move-past org-babel-haxe--imports-re) ; if imports are defined, move past them + (insert "public static function main() {\n") + (indent-code-rigidly (point) (point-max) 4) + (goto-char (point-max)) + (insert "\n}")) + + ;; wrap class. If there's no class, wrap everything in a + ;; generic class. + (goto-char (point-min)) + (when (not (re-search-forward org-babel-haxe--class-re nil t)) + (org-babel-haxe--move-past org-babel-haxe--package-re) ; if package is defined, move past it + (org-babel-haxe--move-past org-babel-haxe--imports-re) ; if imports are defined, move past them + (insert (concat "\nclass " (file-name-base classname) " {\n")) + (indent-code-rigidly (point) (point-max) 4) + (goto-char (point-max)) + (insert "\n}")) + (goto-char (point-min)) + + ;; insert variables from source block headers + (when var-lines + (goto-char (point-min)) + (org-babel-haxe--move-past org-babel-haxe--class-re) ; move inside class + (insert (mapconcat 'identity var-lines "\n")) + (insert "\n")) + + ;; add imports from source block headers + (when imports + (goto-char (point-min)) + (org-babel-haxe--move-past org-babel-haxe--package-re) ; if package is defined, move past it + (insert (mapconcat (lambda (package) (concat "import " package ";")) imports "\n") "\n")) + + ;; add package at the top + (goto-char (point-min)) + (when (and packagename (not (re-search-forward org-babel-haxe--package-re nil t))) + (insert (concat "package " packagename ";\n"))) + + ;; return expanded body + (buffer-string)))) + +(defun org-babel-variable-assignments:haxe (params) + "Return a list of haxe statements assigning the block's variables. +variables are contained in PARAMS." + (mapcar + (lambda (pair) + (format " static var %s %s = %s;" + (car pair) ; name + (if (and (sequencep (cdr pair)) ; type + (not (stringp (cdr pair)))) + ":Array<Dynamic> " "") + (org-babel-haxe-var-to-haxe (cdr pair)))) ; value + (org-babel--get-vars params))) + +(defun org-babel-haxe-var-to-haxe (var) + "Convert an elisp value to a haxe variable. +Convert an elisp value, VAR, into a string of haxe source code +specifying a variable of the same value." + (cond ((and (sequencep var) + (not (stringp var))) + (concat "[" (mapconcat #'org-babel-haxe-var-to-haxe var ", ") "]")) + ((equal var 'hline) + org-babel-haxe-hline-to) + (t + (format "%S" (if (stringp var) (substring-no-properties var) var))))) + +(defun org-babel-haxe-table-or-string (results) + "Convert RESULTS into an appropriate elisp value. +If the results look like a list or vector, then convert them into an +Emacs-lisp table, otherwise return the results as a string." + (let ((res (org-babel-script-escape results))) + (if (listp res) + (mapcar (lambda (el) (if (eq 'null el) + org-babel-haxe-null-to + el)) + res) + res))) + +(defun org-babel-haxe-evaluate (cmd result-type result-params result-file) + "Evaluate using an external haxe process. +CMD the command to execute. + +If RESULT-TYPE equals `output' then return standard output as a +string. If RESULT-TYPE equals `value' then return the value +returned by the source block, as elisp. + +RESULT-PARAMS input params used to format the reponse. + +RESULT-FILE filename of the tempfile to store the returned value in +for `value' RESULT-TYPE. Not used for `output' RESULT-TYPE." + (let ((raw (pcase result-type + (`output (org-babel-eval cmd "")) + (`value (org-babel-eval cmd "") + (org-babel-eval-read-file result-file))))) + (org-babel-result-cond result-params raw + (org-babel-haxe-table-or-string raw)))) + +(provide 'ob-haxe) + +;;; ob-haxe.el ends here diff --git a/test-ob-haxe.el b/test-ob-haxe.el new file mode 100644 index 0000000..e347500 --- /dev/null +++ b/test-ob-haxe.el @@ -0,0 +1,552 @@ +;;; test-ob-haxe.el --- tests for ob-haxe.el + +;; Copyright (c) 2020-2021 Free Software Foundation, Inc. +;; Author: Ian Martins + +;; This file is not part of GNU Emacs. + +;; 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 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. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Code: +(require 'org-test) + +(require 'ob-core) +(defvar org-babel-temporary-directory ; from ob-core + (if (boundp 'org-babel-temporary-directory) + org-babel-temporary-directory + (temporary-file-directory))) + +(org-test-for-executable "haxe") +(org-test-for-executable "neko") +(org-test-for-executable "hl") +(unless (featurep 'ob-haxe) + (signal 'missing-test-dependency "Support for haxe code blocks")) + +; simple tests + +(ert-deftest ob-haxe/simple () + "Hello world program that writes output." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +Sys.print(42); +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-bracket () + "Hello world program that outputs an open square bracket." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +Sys.print(\"[42\"); +#+end_src" + (should (string= "[42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-quote () + "Hello world program that writes quotes." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +Sys.print(\"\\\"42\\\"\"); +#+end_src" + (should (string= "\"42\"" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-return-int () + "Hello world program that returns an int value. Also tests +that ob-haxe defaults to scripting mode." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil silent +return 42; +#+end_src" + (should (eq 42 (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-return-float () + "Hello world program that returns a float value." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results value silent +return 42.1; +#+end_src" + (should (equal 42.1 (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-return-string () + "Hello world program that returns a string value." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results value silent +return \"forty two\"; +#+end_src" + (should (string= "forty two" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-return-int-neko () + "Hello world program that returns an int value. Also tests +that ob-haxe defaults to scripting mode." + (org-test-with-temp-text + "#+begin_src haxe :target neko :dir 'nil silent +return 42; +#+end_src" + (should (eq 42 (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-return-int-hl () + "Hello world program that returns an int value. Also tests +that ob-haxe defaults to scripting mode." + (org-test-with-temp-text + "#+begin_src haxe :target hl :dir 'nil silent +return 42; +#+end_src" + (should (eq 42 (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-main () + "Hello world program that defines a main function." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +public static function main() { + Sys.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-two-methods () + "Hello world program with two methods and no class." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +public static function main() { + Sys.print(foo()); +} +public static function foo() { + return 42; +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-no-main () + "Hello world program with no main method. Babel adds a dummy one so it can run without error." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +public static function foo() { + return 42; +} +#+end_src" + (should (string= "success" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-main-args-array () + "Hello world program that defines a main function with the square brackets after `args'." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +public static function main() { + Sys.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-main-whitespace () + "Hello world program that defines a main function with the square brackets after `args'." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +public +static +function +main + ( +) +{ + Sys.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-class () + "Hello world program that defines a class." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +class Simple { + public static function main() { + Sys.print(42); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-class-and-package () + "Hello world program that defines a class and package." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent +package pkg; +class Simple { + public static function main() { + Sys.print(42); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-class-attr () + "Hello world program with class header attribute." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent :classname Simple +public static function main() { + Sys.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/simple-with-class-attr-with-package () + "Hello world program with class attr with package." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output silent :classname pkg.Simple +public static function main() { + Sys.print(42); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + + +;; var tests + +(ert-deftest ob-haxe/integer-var () + "Read and write an integer variable." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=42 :results output silent +Sys.print(a); +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/var-with-main () + "Read and write an integer variable, with main function provided." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=42 :results output silent +public static function main() { + Sys.print(a); +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/var-with-class () + "Read and write an integer variable, with class provided." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=42 :results output silent +class Main { + public static function main() { + Sys.print(a); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/var-with-class-and-package () + "Read and write an integer variable, with class and package provided." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=42 :results output silent +package pkg; +class Main { + public static function main() { + Sys.print(a); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/var-with-class-and-hanging-curlies () + "Read and write an integer variable, with class with hanging curlies." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=42 :results output silent +class Main +{ + public static function main() + { + Sys.print(a); + } +} +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/two-vars () + "Read two integer variables, combine and write them." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=21 b=2 :results output silent +Sys.print(a*b); +#+end_src" + (should (string= "42" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/string-var () + "Read and write a string variable." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=\"forty two\" :results output silent +Sys.print('$a, len=${a.length}'); +#+end_src" + (should (string= "forty two, len=9" (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/multiline-string-var () + "Haxe doesn't support multiline string literals, so this errors." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=\"forty\ntwo\" :results output silent +Sys.print(String.format(\"%s, len=%d\", a, a.length())); +#+end_src" + (should-error (org-babel-execute-src-block))) + :type 'error) + +;; return array + +(ert-deftest ob-haxe/return-vector-using-array () + "Return a vector using an array." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results value vector silent +return [[4], [2]]; +#+end_src" + (should (equal '((4) (2)) + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/read-return-array () + "Read and return an array." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=haxe_list :results value silent +return [a[0][0], a[1][0]]; +#+end_src + +#+name: haxe_list +- forty +- two" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/read-return-array-with-package () + "Read and return an array with package." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=haxe_list :results value silent +package pkg; +return [a[0][0], a[1][0]]; +#+end_src + +#+name: haxe_list +- forty +- two" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/output-list-with-spaces () + "Return a vector." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :results output list raw silent +Sys.println(\"forty two\"); +Sys.println(\"forty two\"); +#+end_src" + (should (equal "forty two\nforty two\n" + (org-babel-execute-src-block))))) + +;; list vars + +(ert-deftest ob-haxe/list-var () + "Read and write a list variable." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a='(\"forty\" \"two\") :results value silent +var b = a; +return b; +#+end_src" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/vector-var () + "Read and write a vector variable." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a='[\"forty\" \"two\"] :results value silent +var b = a; +return b; +#+end_src" + (should (equal '("forty" "two") + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/matrix-var () + "Read and write matrix variable." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=haxe_matrix :results value silent +var b = [[a[0][0], a[1][0]], + [a[0][1], a[1][1]]]; +return b; // transpose +#+end_src + +#+name: haxe_matrix +| 2 | 1 | +| 4 | 2 |" + (should (equal '((2 4) (1 2)) + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/matrix-var-with-header () + "Read matrix variable and write it with header." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=haxe_matrix :results value table silent +var b = [[\"col1\", \"col2\"], + null, + [a[0][0], a[1][0]], + [a[0][1], a[1][1]]]; +return b; // transpose +#+end_src + +#+name: haxe_matrix +| 2 | 1 | +| 4 | 2 |" + (should (equal '(("col1" "col2") hline (2 4) (1 2)) + (org-babel-execute-src-block))))) + +;; output table + +(ert-deftest ob-haxe/output-table-with-header () + "Write a table that includes a header." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=haxe_matrix :results output raw table silent +Sys.println(\"|col1|col2|\"); +Sys.println(\"|-\"); +for (ii in 0...a.length) { + for (jj in 0...a[0].length) { + Sys.print('|${a[ii][jj]}'); + } + Sys.println(\"\"); + } +#+end_src + +#+name: haxe_matrix +| 2 | 1 | +| 4 | 2 |" + (should (equal "|col1|col2|\n|-\n|2|1\n|4|2\n" + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/inhomogeneous_table () + "Read and write an inhomogeneous table." + (org-test-with-temp-text + "#+begin_src haxe :dir 'nil :var a=haxe_table :results value silent +return [[a[0][0], a[0][1]*2], + [a[1][0], a[1][1]*2]]; +#+end_src + +#+name: haxe_table + | string | number | + |--------+--------| + | forty | 2 | + | two | 1 |" + (should (equal + '(("forty" 4) ("two" 2)) + (org-babel-execute-src-block))))) + +;; imports + +(ert-deftest ob-haxe/import_library () + "Import a standard haxe library." + (org-test-with-temp-text + "#+begin_src haxe :results output silent :imports haxe.crypto.Base64 haxe.io.Bytes + var encoded = Base64.encode(Bytes.ofString(\"42\")); + var decoded = Base64.decode(encoded); + Sys.print('encoded=$encoded, decoded=$decoded'); +#+end_src" + (should (string= + "encoded=NDI=, decoded=42" + (org-babel-execute-src-block))))) + +(ert-deftest ob-haxe/import_library_inline () + "Import a standard haxe library." + (org-test-with-temp-text + "#+begin_src haxe :results output silent + import haxe.crypto.Base64; + import haxe.io.Bytes; + var encoded = Base64.encode(Bytes.ofString(\"42\")); + var decoded = Base64.decode(encoded); + Sys.print('encoded=$encoded, decoded=$decoded'); +#+end_src" + (should (string= + "encoded=NDI=, decoded=42" + (org-babel-execute-src-block))))) + +;; tangle + +(ert-deftest ob-haxe/tangle () + "Tangle a source block." + (org-test-with-temp-text-in-file + "#+begin_src haxe :dir 'nil :tangle \"Tangle.hx\" :results value :classname Tangle +return \"tangled\"; +#+end_src" + (should + (string= + "class Tangle { + public static function main() { + return \"tangled\"; + } +} +" + (unwind-protect + (progn (org-babel-tangle) + (with-temp-buffer + (insert-file-contents "Tangle.hx") + (untabify (point-min) (point-max)) + (buffer-string))) + (delete-file "Tangle.hx")))))) + +(ert-deftest ob-haxe/tangle-with-package () + "Tangle a source block." + (org-test-with-temp-text-in-file + "#+begin_src haxe :dir 'nil :tangle \"tangle/Tangle.hx\" :results value :classname tangle.Tangle +return \"tangled\"; +#+end_src" + (should + (string= + "package tangle; + +class Tangle { + public static function main() { + return \"tangled\"; + } +} +" + (unwind-protect + (progn + (make-directory "tangle") + (org-babel-tangle) + (with-temp-buffer + (insert-file-contents "tangle/Tangle.hx") + (untabify (point-min) (point-max)) + (buffer-string))) + (delete-file "tangle/Tangle.hx") + (delete-directory "tangle")))))) + + +;; specify output dir + +(ert-deftest ob-haxe/simple-dir () + "Hello world program that writes output." + (org-test-with-temp-text + (format "#+begin_src haxe :dir %s :results output silent +Sys.print(42); +#+end_src" org-babel-temporary-directory) + (should (string= + "42" + (unwind-protect + (org-babel-execute-src-block) + (delete-file (concat (file-name-as-directory org-babel-temporary-directory) + "Main.hx"))))))) + +(ert-deftest ob-haxe/simple-dir-with-package () + "Hello world program that writes output." + (org-test-with-temp-text + (format "#+begin_src haxe :dir %s :results output silent +package pkg; + +class Main { + public static function main() { + Sys.print(42); + } +} +#+end_src" org-babel-temporary-directory) + (should (string= + "42" + (unwind-protect + (org-babel-execute-src-block) + (delete-file (concat (file-name-as-directory org-babel-temporary-directory) + "pkg/Main.hx")) + (delete-directory (concat (file-name-as-directory org-babel-temporary-directory) + "pkg"))))))) + + +;;; test-ob-haxe.el ends here