jslink.scm 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. ;;; jslink --- Link Together JS Modules
  2. ;; Copyright 2017 Free Software Foundation, Inc.
  3. ;;
  4. ;; This program is free software; you can redistribute it and/or
  5. ;; modify it under the terms of the GNU Lesser General Public License
  6. ;; as published by the Free Software Foundation; either version 3, or
  7. ;; (at your option) any later version.
  8. ;;
  9. ;; This program is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Lesser General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Lesser General Public
  15. ;; License along with this software; see the file COPYING.LESSER. If
  16. ;; not, write to the Free Software Foundation, Inc., 51 Franklin
  17. ;; Street, Fifth Floor, Boston, MA 02110-1301 USA
  18. ;;; Author: Ian Price <ianprice90@gmail.com>
  19. ;;; Commentary:
  20. ;; Usage: jslink [ARGS]
  21. ;;
  22. ;; A command-line tool for linking together compiled JS modules.
  23. ;;; Code:
  24. (define-module (scripts jslink)
  25. #:use-module (system base compile)
  26. #:use-module (system base language)
  27. #:use-module (language javascript)
  28. #:use-module (srfi srfi-1)
  29. #:use-module (srfi srfi-37)
  30. #:use-module (ice-9 format)
  31. #:use-module (rnrs bytevectors)
  32. #:use-module (ice-9 binary-ports)
  33. #:export (jslink))
  34. (define %summary "Link a JS module.")
  35. (define* (copy-port from #:optional (to (current-output-port)) #:key (buffer-size 1024))
  36. (define bv (make-bytevector buffer-size))
  37. (let loop ()
  38. (let ((num-read (get-bytevector-n! from bv 0 buffer-size)))
  39. (unless (eof-object? num-read)
  40. (put-bytevector to bv 0 num-read)
  41. (loop)))))
  42. (define boot-dependencies
  43. '(("ice-9/posix" . #f)
  44. ("ice-9/ports" . (ice-9 ports))
  45. ("ice-9/threads" . (ice-9 threads))
  46. ("srfi/srfi-4" . (srfi srfi-4))
  47. ("ice-9/deprecated" . #t)
  48. ("ice-9/boot-9" . #t)
  49. ;; FIXME: needs to be at end, or I get strange errors
  50. ("ice-9/psyntax-pp" . #t)
  51. ))
  52. (define (fail . messages)
  53. (format (current-error-port) "error: ~{~a~}~%" messages)
  54. (exit 1))
  55. (define %options
  56. (list (option '(#\h "help") #f #f
  57. (lambda (opt name arg result)
  58. (alist-cons 'help? #t result)))
  59. (option '("version") #f #f
  60. (lambda (opt name arg result)
  61. (show-version)
  62. (exit 0)))
  63. (option '(#\o "output") #t #f
  64. (lambda (opt name arg result)
  65. (if (assoc-ref result 'output-file)
  66. (fail "`-o' option cannot be specified more than once")
  67. (alist-cons 'output-file arg result))))
  68. (option '(#\d "depends") #t #f
  69. (lambda (opt name arg result)
  70. (define (read-from-string s)
  71. (call-with-input-string s read))
  72. (let ((depends (assoc-ref result 'depends)))
  73. (alist-cons 'depends (cons (read-from-string arg) depends)
  74. result))))
  75. (option '("no-boot") #f #f
  76. (lambda (opt name arg result)
  77. (alist-cons 'no-boot? #t result)))
  78. ))
  79. (define (parse-args args)
  80. "Parse argument list @var{args} and return an alist with all the relevant
  81. options."
  82. (args-fold args %options
  83. (lambda (opt name arg result)
  84. (format (current-error-port) "~A: unrecognized option" name)
  85. (exit 1))
  86. (lambda (file result)
  87. (let ((input-files (assoc-ref result 'input-files)))
  88. (alist-cons 'input-files (cons file input-files)
  89. result)))
  90. ;; default option values
  91. '((input-files)
  92. (depends)
  93. (no-boot? . #f)
  94. )))
  95. (define (show-version)
  96. (format #t "compile (GNU Guile) ~A~%" (version))
  97. (format #t "Copyright (C) 2017 Free Software Foundation, Inc.
  98. License LGPLv3+: GNU LGPL version 3 or later <http://gnu.org/licenses/lgpl.html>.
  99. This is free software: you are free to change and redistribute it.
  100. There is NO WARRANTY, to the extent permitted by law.~%"))
  101. (define (show-help)
  102. (format #t "Usage: jslink [OPTION] FILE
  103. Link Javascript FILE with all its dependencies
  104. -h, --help print this help message
  105. -v, --version show version information
  106. -o, --output=OFILE write output to OFILE
  107. -d, --depends=DEP add dependency on DEP
  108. --no-boot link without boot-9 & its dependencies
  109. Report bugs to <~A>.~%"
  110. %guile-bug-report-address))
  111. (define* (link-file file #:key (extra-dependencies '()) output-file no-boot?)
  112. (let ((dependencies (if no-boot?
  113. extra-dependencies
  114. ;; FIXME: extra-dependencies need to come before psyntax
  115. (append extra-dependencies boot-dependencies)))
  116. (output-file (or output-file "main.js")) ;; FIXME: changeable
  117. )
  118. (with-output-to-file output-file
  119. (lambda ()
  120. (format #t "(function () {\n")
  121. (link-runtime)
  122. (format #t "/* ---------- end of runtime ---------- */\n")
  123. (for-each (lambda (x)
  124. (let ((path (car x))
  125. (file (cdr x)))
  126. (link-dependency path file))
  127. (format #t "/* ---------- */\n"))
  128. dependencies)
  129. (format #t "/* ---------- end of dependencies ---------- */\n")
  130. (link-main file no-boot?)
  131. (format #t "})();")
  132. output-file))))
  133. (define *runtime-file* (%search-load-path "language/js-il/runtime.js"))
  134. (define (link-runtime)
  135. (call-with-input-file *runtime-file* copy-port))
  136. (define (link-dependency path file)
  137. (define (compile-dependency file)
  138. (call-with-input-file file
  139. (lambda (in)
  140. ((language-printer (lookup-language 'javascript))
  141. (read-and-compile in
  142. #:from 'scheme
  143. #:to 'javascript
  144. #:env (default-environment (lookup-language 'scheme)))
  145. (current-output-port)))))
  146. (format #t "boot_modules[~s] =\n" path)
  147. (cond ((string? file)
  148. (compile-dependency file))
  149. ((list? file)
  150. (print-statement (compile `(define-module ,file)
  151. #:from 'scheme #:to 'javascript)
  152. (current-output-port))
  153. (newline))
  154. (file (compile-dependency (%search-load-path path)))
  155. (else
  156. (format #t "function (cont) { return cont(scheme.UNDEFINED); };")))
  157. (newline))
  158. (define (link-main file no-boot?)
  159. ;; FIXME: continuation should be changeable with a switch
  160. (call-with-input-file file
  161. (lambda (in)
  162. (format #t "var main =\n")
  163. (copy-port in)
  164. (newline)
  165. (if no-boot?
  166. (format #t "main(scheme.initial_cont);\n")
  167. (format #t "boot_modules[\"ice-9/boot-9\"](function() {return main((function (x) {console.log(x); return x; }));});"))))) ; scheme.initial_cont
  168. (define (jslink . args)
  169. (let* ((options (parse-args args))
  170. (help? (assoc-ref options 'help?))
  171. (dependencies (assoc-ref options 'depends))
  172. (input-files (assoc-ref options 'input-files))
  173. (output-file (assoc-ref options 'output-file))
  174. (no-boot? (assoc-ref options 'no-boot?)))
  175. (if (or help? (null? input-files))
  176. (begin (show-help) (exit 0)))
  177. (unless (null? (cdr input-files))
  178. (fail "can only link one file at a time"))
  179. (format #t "wrote `~A'\n"
  180. (link-file (car input-files)
  181. #:extra-dependencies dependencies
  182. #:output-file output-file
  183. #:no-boot? no-boot?))))
  184. (define main jslink)