💡

Common Lisp to JavaScriptコンパイラー「JSCL」の使い方

2023/12/02に公開

JSCLはCommon LispからJavaScriptへのコンパイラーです。サーバーサイドだけでなく、フロントもCommon Lispで書きたいけど、Parenscriptでは物足りないといった方におすすめです。
https://github.com/jscl-project/jscl

使い方

ソースをダウンロード

git clone https://github.com/jscl-project/jscl.git

ASDFは使わないので場所はどこでもいいです。

Common Lispの処理系を起動

お好きな処理系を起動。

jscl.lispをロード

(load "path/to/jscl/jscl.lisp")

JavaScript上でのCommn Lispのランタイムを作成

(jscl:bootstrap)

これによりjscl.jsが出来上がります。

自作コードのコンパイル

(jscl:compile-application files output)

filesに指定したファイルすべてをコンパイルして、outputで指定したファイルにその結果が出力されます。
※一度(jscl:bootstrap)を実行しても、一旦Common Lispの処理系を終了した場合、再度(jscl:bootstrap)を実行しないとコンパイルはできません。

アプリケーションへの組み込み

コンパイルされたjscl.jsと自作スクリプトファイルをHTMLのscriptタグに指定することでブラウザ上でCommon Lispのコードを実行することができます。

<!DOCTYPE html>
<html>
  <head>
    <script src="jscl.js"></script>
    <script src="{自作スクリプト}"></script>
  </head>
  <body>
  </body>
</html>

JS変数、プロパティへのアクセス

JS側の変数を参照

#j:{JS変数名}
;; 例
#j:window:outerWidth

プロパティを参照

(jscl::oget {JSオブジェクト} {プロパティ名})
;; 例
(setq win #j:window)
(jscl::oget win "outerWidth")

値の変更はsetfで可能です。
関数呼び出し

(#j:{JS変数名} 引数1 ... 引数N)
((jscl::oget {JSオブジェクト} {プロパティ名}) 引数1 ... 引数N)
;; 例
(#j:Math:tan 0.8)

※一旦lispの変数に束縛するとfuncallが要ります。

JavaScriptからCommon Lispのコードを実行する方法

jscl.evaluateString(str)

サンプルプログラム1

example.lisp
(defpackage :jscl-example
  (:use :cl))

(in-package :jscl-example)

(defun fact (n)
  (if (= n 0)
      1
      (* n (fact (1- n)))))

(defun attr (el name)
  (jscl::oget el name))

(defun (setf attr) (value el name)
  (setf (jscl::oget el name) value))

(defun set-attrs (el attrs)
  (loop for (k v) on attrs by #'cddr
	do (setf (attr el k) v)))

(defun create-element (name &optional attrs)
  (let ((el (#j:document:createElement name)))
    (when attrs
      (set-attrs el attrs))
    el))

(defun add-event-listener (el type fn)
  ((jscl::oget el "addEventListener") type fn))

(defun init ()
  (let ((in-el (create-element "input" '("type" "text" "value" "10")))
	(out-el (create-element "input" '("type" "text")))
	(button (create-element "button" '("innerText" "Calc"))))
    (add-event-listener button "click" (lambda (e)
					 ;; ↓ Not supported.
					 ;; (declare (ignore e))
					 (setf (attr out-el "value")
					       (fact (parse-integer (attr in-el "value"))))))
    (#j:document:body:append in-el button out-el)))

(add-event-listener #j:document "DOMContentLoaded" (lambda (e) (init)))

このファイルを「example.js」としてコンパイルします。(ディレクトリはご自由に)

(jscl:compile-application '("~/common-lisp/jscl/example.lisp") "~/common-lisp/jscl/example.js")

「example.js」をHTMLに組み込み
※「example.html」、「jscl.js」、「example.js」は同じディレクトリに配置

example.html
<!DOCTYPE html>
<html>
  <head>
    <script src="jscl.js"></script>
    <script src="example.js"></script>
  </head>
  <body>
  </body>
</html>

ブラウザで「example.html」を開くとこのようになります。
「Calc」ボタンをクリックした結果

サンプルプログラム2

自作のCommon Lisp製のゲームをJSCLに移植しました。
元のものとは違い、セーブはできません。
https://yoshida2koji.github.io/jscl-game.html

注意点

JSCLはCommon Lispのサブセットなので実装されていない機能もあります。以下、私が確認したものです。(2023/12/02現在)

  • arefが多次元配列に対応していない
  • equalpがない
  • sin cos tanがない
  • 分数リテラル(1/2など)が使えない

また、実行時にエラーが発生した場合、そのエラー原因を特定するのはなかなか難しいです。

Discussion