about summary refs log tree commit diff
;;; ravanan --- High-reproducibility CWL runner powered by Guix
;;; Copyright © 2024–2025 Arun Isaac <arunisaac@systemreboot.net>
;;;
;;; This file is part of ravanan.
;;;
;;; ravanan 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.
;;;
;;; ravanan 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 ravanan.  If not, see <https://www.gnu.org/licenses/>.

(use-modules (srfi srfi-64)
             (guix gexp)
             (ice-9 match)
             (ravanan work monads)
             (ravanan work utils)
             (ravanan javascript))

(define (gexp->sexp-rec exp)
  "Recursively convert G-expression @var{exp} into an approximate S-expression."
  (match exp
    ((? gexp? exp)
     (gexp->sexp-rec (gexp->approximate-sexp exp)))
    ((head tail ...)
     (cons (gexp->sexp-rec head)
           (map gexp->sexp-rec tail)))
    (atom
     atom)))

(test-begin "javascript")

(test-equal "evaluate parameter reference"
  "c"
  (evaluate-javascript-expression "$(inputs.message['bar'][\"foo\"][2])"
                                  '(("inputs" ("message" ("bar" ("foo" . #("a" "b" "c" "d"))))))))

(test-equal "evaluate null parameter reference"
  'null
  (evaluate-javascript-expression "$(null)"
                                  '()))

(test-equal "evaluate true javascript expression"
  #t
  (evaluate-javascript-expression "$(true)"
                                  '()))

(test-equal "evaluate false javascript expression"
  #f
  (evaluate-javascript-expression "$(false)"
                                  '()))

(test-equal "evaluate parameter reference to JSON object"
  (canonicalize-json '(("class" . "File")
                       ("path" . "/foo/bar")))
  (canonicalize-json
   (evaluate-javascript-expression "$(inputs.fasta)"
                                   '(("inputs" ("fasta"
                                                ("class" . "File")
                                                ("path" . "/foo/bar")))))))

(test-equal "evaluate parameter reference with string interpolation"
  "24foo12foobar"
  (evaluate-javascript-expression "$(runtime.cores)foo$(inputs.threads)$(inputs.output_filename)"
                                  '(("inputs"
                                     ("threads" . 12)
                                     ("output_filename" . "foobar"))
                                    ("runtime" ("cores" . 24)))))

(test-equal "evaluate parameter reference with string interpolation of JSON trees"
  "foo[0,1,2,3]{\"bar\":2,\"foo\":1}"
  (evaluate-javascript-expression "foo$(inputs.vector)$(inputs.object)"
                                  '(("inputs"
                                     ("object"
                                      ("foo" . 1)
                                      ("bar" . 2))
                                     ("vector" . #(0 1 2 3))))))

(test-equal "evaluate parameter reference with node"
  3
  (evaluate-javascript-expression "$(inputs.n + 1)"
                                  '(("inputs" ("n" . 2)))))

(test-equal "evaluate null parameter reference with node"
  ''null
  (gexp->sexp-rec (evaluate-javascript-expression "$(null)")))

(test-equal "evaluate true javascript expression with node"
  '#t
  (gexp->sexp-rec (evaluate-javascript-expression "$(true)")))

(test-equal "evaluate false javascript expression with node"
  '#f
  (gexp->sexp-rec (evaluate-javascript-expression "$(false)")))

(test-equal "evaluate parameter reference to JSON object using node"
  '(json-ref inputs "fasta")
  (gexp->sexp-rec
   (evaluate-javascript-expression "$(inputs.fasta)")))

(test-equal "evaluate parameter reference with string interpolation using node"
  "24foo24foobar"
  (evaluate-javascript-expression "$(runtime.cores)foo$(inputs.threads*2)$(inputs.output_filename)"
                                  '(("inputs"
                                     ("threads" . 12)
                                     ("output_filename" . "foobar"))
                                    ("runtime" ("cores" . 24)))))

(test-equal "evaluate parameter reference with string interpolation of JSON trees using node"
  "foo[0,1,2,3]{\"bar\":2,\"foo\":1}20"
  (evaluate-javascript-expression "foo$(inputs.vector)$(inputs.object)$(inputs.object.foo*20)"
                                  '(("inputs"
                                     ("object"
                                      ("foo" . 1)
                                      ("bar" . 2))
                                     ("vector" . #(0 1 2 3))))))

(test-equal "evaluate parameter reference (without context)"
  '(json-ref inputs "message" "bar" "foo" 2)
  (gexp->sexp-rec
   (evaluate-javascript-expression "$(inputs.message['bar'][\"foo\"][2])")))

(test-equal "evaluate parameter reference with string interpolation (without context)"
  '(string-join
    (map (lambda (value)
           (if (string? value) value (scm->json-string (canonicalize-json value))))
         (list (json-ref runtime "cores")
               "foo"
               (json-ref inputs "threads")
               (json-ref inputs "output_filename")))
    "")
  (gexp->sexp-rec
   (evaluate-javascript-expression "$(runtime.cores)foo$(inputs.threads)$(inputs.output_filename)")))

(test-equal "evaluate parameter reference with string interpolation of JSON trees (without context)"
  '(string-join
    (map (lambda (value)
           (if (string? value) value (scm->json-string (canonicalize-json value))))
         (list "foo" (json-ref inputs "vector") (json-ref inputs "object")))
    "")
  (gexp->sexp-rec
   (evaluate-javascript-expression "foo$(inputs.vector)$(inputs.object)")))

(test-equal "evaluate parameter reference with node (without context)"
  '(evaluate-javascript (*approximate*)
                        "(inputs.n + 1)"
                        (string-append ""
                                       "var inputs = " (scm->json-string inputs) ";"
                                       "var self = " (scm->json-string self) ";"
                                       "var runtime = " (scm->json-string runtime) ";"))
  (gexp->sexp-rec
   (evaluate-javascript-expression "$(inputs.n + 1)")))

(test-equal "evaluate parameter reference with string interpolation using node (without context)"
  '(string-join
    (map (lambda (value)
           (if (string? value) value (scm->json-string (canonicalize-json value))))
         (list (json-ref runtime "cores")
               "foo"
               (evaluate-javascript (*approximate*)
                                    "(inputs.threads*2)"
                                    (string-append ""
                                                   "var inputs = " (scm->json-string inputs) ";"
                                                   "var self = " (scm->json-string self) ";"
                                                   "var runtime = " (scm->json-string runtime) ";"))
               (json-ref inputs "output_filename")))
    "")
  (gexp->sexp-rec
   (evaluate-javascript-expression "$(runtime.cores)foo$(inputs.threads*2)$(inputs.output_filename)")))

(test-equal "evaluate parameter reference with string interpolation of JSON trees using node (without context)"
  '(string-join
    (map (lambda (value)
           (if (string? value) value (scm->json-string (canonicalize-json value))))
         (list "foo"
               (json-ref inputs "vector")
               (json-ref inputs "object")
               (evaluate-javascript (*approximate*)
                                    "(inputs.object.foo*20)"
                                    (string-append ""
                                                   "var inputs = " (scm->json-string inputs) ";"
                                                   "var self = " (scm->json-string self) ";"
                                                   "var runtime = " (scm->json-string runtime) ";"))))
    "")
  (gexp->sexp-rec
   (evaluate-javascript-expression "foo$(inputs.vector)$(inputs.object)$(inputs.object.foo*20)")))

(test-equal "evaluate javascript expression with parentheses"
  0
  (evaluate-javascript-expression "$(1 - (2 - 1))"
                                  '()))

(test-equal "evaluate javascript function body"
  2
  (evaluate-javascript-expression "${switch (inputs.message) { case \"foo\": return 1; case \"bar\": return 2; default: return 3}}"
                                  '(("inputs" . (("message" . "bar")
                                                 ("threads" . 48)))
                                    ("self" . #f)
                                    ("runtime" . #f))))

(test-error "missing key in parameter reference must raise an error" #t
            (evaluate-javascript-expression "$(inputs.fbar)"
                                            '(("inputs" . ()))
                                            '()))

(test-equal "trim whitespace characters in javascript expressions"
  2
  (evaluate-javascript-expression " $(1 + 1)\n"
                                  '()))

(test-end "javascript")