ABCL 可以用 compile-file 將 Lisp 原始碼編譯成 .abcl FASL 檔案。這種檔案包含 Java 位元組碼與 ABCL 載入資訊,不能直接當作 JAR 使用;本文示範如何編譯、載入單一檔案,以及編譯整個 ASDF 系統。
在取得ASDF系統資源路徑一文中曾提到,使用asdf:load-system時,實際載入的檔案可能不是原始Lisp檔案,因此不能總是依賴*load-pathname*或*load-truename*定位原始碼。
和其他Common Lisp實作一樣,ABCL也提供HyperSpec中的compile-file,可將原始碼編譯成方便快速載入的 FASL(Fast Load)檔案。由於 ABCL 執行於 Java 平台,編譯結果不是機器碼,而是 Java 位元組碼(Java bytecode);輸出檔案使用 .abcl 格式,而不是可直接使用的 .class 檔案。
ECL 也提供
compile-file。由於它以 C 語言實作,還支援多種輸出目標格式:
- Portable FASL file (.fasc)
- Native FASL file (.fas, .fasb)
- Object file (.o)
- Static library
- Shared library
- Executable file
可參考:https://ecl.common-lisp.dev/static/files/manual/current-manual/System-building.html#Compiling-with-ECL-1
Common Lisp 可以直接載入原始碼,也可以先編譯再載入。因此,asdf:load-system不一定直接載入原始碼;依照實作和設定,它可能先編譯,再載入編譯結果。在我的環境執行abcl --noinform --noinit --load run-system.lisp --eval '(quit)',可以看到載入的是${HOME}/.cache/common-lisp/abcl-1.9.2-fasl43-linux-x64/目錄下的.abcl檔案。
其實Python、Lua等很多相似語言都存在這樣的中間態(
.pyc、.luac)。
以lesson1 - Hello abclFX中的main.lisp為例,在終端機執行以下指令:
abcl --noinform --noinit --eval '(compile-file "main.lisp")' --eval '(quit)'
# or
abcl --noinform --noinit --eval '(compile-file "main.lisp")' --batch
執行後會產生編譯結果main.abcl。.abcl檔案實際上是ZIP壓縮檔:
➜ lesson1 - Hello abclFX $ file main.abcl
main.abcl: Zip archive data, at least v2.0 to extract, compression method=deflate
使用unzip -l main.abcl查看壓縮檔內容:
Archive: main.abcl
Length Date Time Name
--------- ---------- ----- ----
3307 2026-10-06 22:29 __loader__._
3287 2026-10-06 22:29 main_1.cls
1371 2026-10-06 22:29 main_2.cls
2869 2026-10-06 22:29 main_3.cls
--------- -------
10834 4 files
以上結構和JAR檔案很像:兩者都是ZIP壓縮檔,也都包含Java位元組碼;但.abcl不能直接當作JAR檔案使用。
載入器檔案__loader__._的內容是以S-expression表示的純文字;副檔名為.cls的檔案則是Java class檔案的變形格式。
類似於有些JAR檔案會有
MANIFEST.MF,以描述檔案特性。
$ file *.cls
main_1.cls: compiled Java class data, version 49.0 (Java 1.5)
main_2.cls: compiled Java class data, version 49.0 (Java 1.5)
main_3.cls: compiled Java class data, version 49.0 (Java 1.5)
使用 https://jar.tools 反編譯main_2.cls,可以看到以下內容:
/*
* Decompiled with https://jar.tools
*/
package org.armedbear.lisp;
import org.armedbear.lisp.ArgumentListProcessor;
import org.armedbear.lisp.ClosureBinding;
import org.armedbear.lisp.CompiledClosure;
import org.armedbear.lisp.Lisp;
import org.armedbear.lisp.LispObject;
import org.armedbear.lisp.Load;
import org.armedbear.lisp.Symbol;
public final class main_2
extends CompiledClosure {
static final Symbol SYM175 = (Symbol)Load.getUninternedSymbol(0);
public final LispObject execute(LispObject var1) {
ClosureBinding[] var3 = this.ctx;
var3[0].value = var1;
return Lisp.nonLocalGo(var3[1].value, SYM175);
}
public main_2() {
ArgumentListProcessor.OptionalParam[] var1 = new ArgumentListProcessor.OptionalParam[0];
ArgumentListProcessor.KeywordParam[] var2 = new ArgumentListProcessor.KeywordParam[0];
ArgumentListProcessor var3 = new ArgumentListProcessor() /* <init> missing? */;
super(new ArgumentListProcessor(null, 1, var1, var2, false, false));
var3.setFunction(this);
}
}
main_1.cls並未完整解讀成Java程式碼,仍保留大量類組語的操作內容。
至於main_3.cls和main_2.cls差不多,不過有個比較特別的片段:使用了Lisp.readObjectFromString(),參數內容同樣放了小段LISP程式碼的字串。(我好像看到了寫在HelloFX的:methods了XD)(("main" #1=:VOID #2=NIL (#3=LAMBDA (#4=THIS) (#5=JSTATIC "launch" +APPLICATION+ (JOBJECT-CLASS #4# ) (JNEW-ARRAY "java.lang.String" 1)))) ("start" #1# ("javafx.stage.Stage") (#3# (#4# #6=STAGE) (PROG* ((#7=JAVA-VERSION (#5# "getProperty" "java.lang.System" "java.version")) (#8=JAVAFX-VERSION (#5# "getProperty" "java.lang.System" "javafx.version")) (#9=L (#10=JNEW +LABEL+ (FORMAT #2# "Hello, JavaFX ~A, running on Java ~A." #8# #7# ))) (#11=SCENE (#10# +SCENE+ (#10# +STACKPANE+ (JARRAY-FROM-LIST (SYSTEM::BACKQ-LIST #9# ))) 640 480))) (#12=JCALL "setScene" #6# #11# ) (#12# "show" #6# )))))
.abcl 檔案.abcl檔案同樣可以用#'load載入:
(load "main.abcl")
呼叫load時也可以省略副檔名;ABCL會先嘗試載入.abcl,找不到時再嘗試.lisp。
(load "main")
接著以lesson14 - CalcFX為例,示範如何編譯整個ASDF系統:(asdf:compile-system "abcl-javafx")
(require :abcl-contrib)
(require :asdf)
(require :abcl-asdf)
;; 禁用output-translations,讓編譯結果直接在原始檔旁邊
(asdf:disable-output-translations)
(push #P"./" asdf:*central-registry*)
(asdf:compile-system "abcl-javafx")
下一篇會接著示範如何將整個 ASDF 系統打包成 JAR 檔案。