前言
網頁原本是靜態的文件載體,現在已經變成最廣泛使用的跨平台運行環境之一。許多的軟體專案都有將特定程式碼轉譯成 JavaScript 程式碼的轉譯器,為了就是搭網頁技術這班順風車。
本文介紹將 Pascal 程式轉為 JavaScript 程式的軟體專案。或許可以藉此延續或拓展 Pascal 程式的生命週期。
注意事項
雖然 pas2js
有持續開發和維護,但 pas2js
目前只能算是堪用,尚未達到實用的程度。主要的原因是 pas2js
的文件過少。除了官方文件外幾乎沒有什麼能看的資料,碰到問題只能藉由觀看少量文件或是 pas2js
的原始碼來解決。對於實務來說開發效率過差。
經轉換後的 JavaScript 命令稿,是在 JavaScript 運行環境 (瀏覽器、Node.js) 下運行,而非原本的 Pascal 運行環境。還是得測試轉換後的 JavaScript 命令稿,確認該命令稿的行為符合預期。
除了本文所介紹的 pas2js
外,網路上還可以找到幾個類似的軟體專案。但除了本文所介紹的軟體外,其他的專案和 Free Pascal 官方團隊皆無關。
pas2js 的工作原理
pas2js
本質上是 Pascal 轉 JavaScript 的轉譯器。該軟體會掃描輸入的 Pascal 程式碼,將其轉換為等效的 JavaScript 程式碼。我們可將轉換過的 JavaScript 命令稿拿到瀏覽器或 Node.js 上執行。
要注意 pas2js
所接受的輸入是 Pascal 程式碼,無法接受二進位檔案。在 pas2js
專案中,也是以原始碼的形式提供轉換後所需的小型運行環境 (runtime environment) 和一些常見的 pas2js
函式庫,包括 jQuery。
安裝 pas2js
在 Windows 上安裝
到 pas2js
提供的下載點 (ftp://ftpmaster.freepascal.org/fpc/contrib/pas2js) 下載 Windows 版本的 pas2js
。將 pas2js
的壓縮檔解壓縮後放至任意位置,像是 C:\ProgramData 就是一個不錯的位置。
以 C:\ProgramData 為例,將 C:\ProgramData\pas2js-windows-1.4.8\bin 加到 PATH 變數中,就可以直接在命令列環境中呼叫 pas2js
。
在 macOS 上安裝
到 pas2js
提供的下載點 (ftp://ftpmaster.freepascal.org/fpc/contrib/pas2js) 下載 macOS 版本的 pas2js
。將 pas2js
的壓縮檔解壓縮後放至任意位置,像是 /opt 或是 $HOME/opt 就是一個不錯的位置。
筆者實測時,macOS 版的 pas2js
似乎無法正常讀取 pas2js
執行檔所在的設定檔。處理的方式是將 path/to/pas2js-macos-1.4.8/bin/pas2js.cfg 拷貝到 $HOME/.pas2js.cfg (注意設定檔的 .
前綴)。
可參考以下範例來設置:
#
# Minimal config file for pas2js compiler
#
# -d is the same as #DEFINE
# -u is the same as #UNDEF
#
# Write always a nice logo ;)
-l
# Display Warnings, Notes and Hints
-vwnh
# If you don't want so much verbosity use
#-vw
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/chartjs
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/dataabstract
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/fcl-base
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/fcl-db
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/fpcunit
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/jspdf
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/nodejs
-Fu/Users/user/opt/pas2js-macos-1.4.18/packages/rtl
#IFDEF nodejs
-Jirtl.js
#ENDIF
# Put all generated JavaScript into one file
-Jc
# end.
經筆者實測,直接在 pas2js.cfg 中寫死絕對路徑會比較簡單。 pas2js.cfg 只有在自己的系統上使用,不用考慮跨平台的議題。
在 GNU/Linux 上安裝
到 pas2js
提供的下載點 (ftp://ftpmaster.freepascal.org/fpc/contrib/pas2js) 下載 GNU/Linux 版本的 pas2js
。將 pas2js
的壓縮檔解壓縮後放至任意位置,像是 /opt 或是 $HOME/opt 就是一個不錯的位置。
以 $HOME/opt 為例,將 $HOME/opt/pas2js-linux-1.4.8/bin 加入 PATH 所在的位置,即可在命令列環境中呼叫 pas2js
。
撰寫第一隻程式
Hello World
用編輯器新增 hello.pas 檔案,在該檔案輸入以下內容:
program main;
begin
WriteLn('Hello World');
end.
基本上,就是 Pascal 版本的 Hello World 程式。
將 Pascal 程式碼轉為網頁前端程式
用 pas2js
將寫好的 Hello World 程式轉為適用於瀏覽器的 JavaScript 程式碼:
$ pas2js -Jc -Jirtl.js hello.pas
-Jc
表示將生成的 JavaScript 程式碼串接成一個大檔案。-Jirtl.js
表示將 pas2js
附帶的小型運行環境一起包進去。
撰寫一個空 (dummy) 頁面來載入轉譯出來的 hello.js 命令稿:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<script src="hello.js">
<script>
rtl.run();
</script>
</body>
</html>
載入該網頁時,應該可以在開發者主控台 (developer console) 看到 Hello World
字串。
若不想把運行期函式庫 (rtl.js) 包進 JavaScript 命令稿,就要自行將 pas2js
專案所提供的 rtl.js 命令稿載入網頁中,否則轉換的 JavaScript 命令稿會無法運作。 rtl.js 命令稿位於 pas2js-platform-version/package/rtl 目錄中。
將 Pascal 程式碼轉為 Node.js 程式
在預設情形下,pas2js
轉出來的 JavaScript 命令稿適用於瀏覽器。不過,pas2js
也可轉出適用於 Node.js 的 JavaScript 命令稿。將指令修改如下:
$ pas2js -Jc -Jirtl.js -Tnodejs hello.pas
-Tnodejs
代表將目標環境設為 Node.js。
使用 Node.js 提供的直譯器即可執行轉譯出來的命令稿:
$ node hello.js
Hello World
Pascal 和其等效的 JavaScript 程式碼
pas2js
的官方文件展示了 pas2js
將 Pascal 程式碼轉出的等效 JavaScript 程式碼。由於目前 pas2js
的教學甚少,該份文件應該是目前學習 pas2js
最重要的資源。
pas2js 不支援的特性
受限於異質運行環境,pas2js
不支援以下特性 (未全部列出):
- 記憶體管理
class
的解構子- 指標運算
- 運算子重載
- 泛型程式
object
型態variant
型態
其他項目請看 pas2js
的官方文件。
在 Pascal 中使用 JavaScript 程式碼
pas2js
轉換出來的命令稿會在 JavaScript 運行環境跑,和現有的 JavaScript 程式碼能夠互動是相當重要的。著眼於這個議題,pas2js
加入了一些原本在 Pascal 中沒有的語法。
在主程式中塞入 JavaScript 程式碼
由於 pas2js
的目的就是將 Pascal 程式碼轉 JavaScript 程式碼,但必要時仍然可以用 asm
區塊在 Pascal 程式碼中直接插入 JavaScript 程式碼。參考以下的實例:
program main; (* 1 *)
var (* 2 *)
s : string; (* 3 *)
procedure Print(s: string); (* 4 *)
begin (* 5 *)
WriteLn(s); (* 6 *)
end; (* 7 *)
begin (* 8 *)
s := 'Hello World'; (* 9 *)
(* Raw JavaScript code. *) (* 10 *)
asm /* 11 */
console.log('Print from JavaScript: ' + $mod.s); /* 12 */
$mod.Print('Print from Pascal: ' + $mod.s); /* 13 */
end; (* 14 *)
end. (* 15 *)
本程式的關鍵之處在第 11 行至第 14 行。在這段程式碼中,我們使用 asm
區塊置入 JavaScript 程式碼。
asm
區塊的工作原理是 pas2js
會自動忽略 asm
區塊內的程式碼,所以 asm
區塊內的程式碼會原封不動地輸出。因此,我們可以在 asm
區塊內加入任意的 JavaScript 程式碼。
在這個例子中,我們宣告變數 s
,但沒有在 Pascal 程式碼中用到該變數。在預設情形下,pas2js
為了效能考量,會抹去未用到的宣告和定義。為了避免 pas2js
把變數抹去,我們將程式碼優化抹去。參考以下指令:
$ pas2js -Jc -Jirtl.js -O- -Tnodejs -omain.js source.pas
在這行指令中,參數 -O-
的作用即為關掉程式碼優化。
用 JavaScript 實作函式和程序
既然 pas2js
允許程式設計者在主程式中插入 JavaScript 程式碼,也可以用 JavaScript 來實作 Pascal 的函式和程序。使用 JavaScript 程式碼實作函式和程序的方式是在函式和程序的宣告後加上 assembler
修飾詞 (modifier)。參考以下範例程式:
program main; (* 1 *)
var (* 2 *)
s : string; (* 3 *)
function Greet(const s: string): string; assembler; (* 4 *)
asm /* 5 */
return 'Hello ' + s; /* 6 */
end; (* 7 *)
procedure Print(const s: string); assembler; (* 8 *)
asm /* 9 */
console.log(s); /* 10 */
end; (* 11 *)
begin (* 12 *)
s := 'Hello World'; (* 13 *)
Print(Greet('World')); (* 14 *)
end. (* 15 *)
在第 4 行至第 7 間,我們以 JavaScript 實作函式,該函式會將字串相接。在第 8 行至第 11 行間,我們以 JavaScript 實作程序,該程序會在終端機印出字串。
由於我們有用到變數,同樣要關掉程式碼優化:
$ pas2js -Jc -Jirtl.js -O- -Tnodejs -omain.js source.pas
使用現有的 JavaScript 函式
在先前的例子中,我們在 Pascal 程式碼中引入 JavaScript 程式碼。但我們也可以直接使用 JavaScript 函式。參考以下範例程式:
program main; (* 1 *)
uses (* 2 *)
SysUtils; (* 3 *)
function Abs(n: real): real; external name 'Math.abs'; (* 4 *)
procedure Log(const s: string); external name 'console.log'; (* 5 *)
begin (* 6 *)
Log(FloatToStr(Abs(-3))); (* 7 *)
end. (* 8 *)
第 4 行的 Abs
函式實際上是 JavaScript 的 Math.abs
函式。由於 Math.abs
函式有回傳值,故以函式來宣告。
第 5 行的 Log
程序實際上是 Javascript 的 console.log
函式。由於 console.log
函式無回傳值,故以程序來宣告。
在函式中使用不定參數
JavaScript 可以用 Argument 物件 實作不定參數函式,pas2js
也支援這項功能。參考以下範例程式:
program main; (* 1 *)
uses JS; (* 2 *)
function Sum(): real; varargs; (* 3 *)
var (* 4 *)
i : integer; (* 5 *)
begin (* 6 *)
result := 0.0; (* 7 *)
for i := 0 to JSArguments.length-1 do (* 8 *)
result := result + real(JSArguments[i]); (* 9 *)
end; (* 10 *)
begin (* 11 *)
WriteLn(Sum(1, 2, 3, 4, 5)); (* 12 *)
end. (* 13 *)
由於本範例程式會用到 JavaScript 物件,故我們在第 2 行引入 JS
函式庫。
在第 3 行至第 10 行間,我們以不定參數實作相加函式。在該函式中,我們從 JSArguments
物件取得參數,然後將函式逐一加總。
使用 JavaScript 的變數或常數
在先前的實例中,我們直接使用 JavaScript 函式。除此之外,我們也可以直接使用 JavaScript 變數或常數。在以下例子中,我們引入 JavaScript 內定的自然對數和圓周率:
program main; (* 1 *)
const (* 2 *)
E: Double; external name 'Math.E'; (* 3 *)
PI: Double; external name 'Math.PI'; (* 4 *)
begin (* 5 *)
WriteLn(E); (* 6 *)
WriteLn(PI); (* 7 *)
end. (* 8 *)
使用 JavaScript 類別
除了使用 JavaScript 變數和函式外,我們也可以使用 JavaScript 物件。在以下範例程式中,我們引入 JavaScript 的 Date 物件:
program main; (* 1 *)
{$modeswitch externalclass} (* 2 *)
type (* 3 *)
TJSDate = class external name 'Date' (* 4 *)
private (* 5 *)
function getFullYear(): NativeInt; (* 6 *)
procedure setFullYear(const value: NativeInt); (* 7 *)
public (* 8 *)
constructor New(); (* 9 *)
constructor New(const ms: NativeInt); (* 10 *)
class function Now: NativeInt; (* 11 *)
property Year: NativeInt read getFullYear write setFullYear; (* 12 *)
end; (* 13 *)
var (* 14 *)
d: TJSDate; (* 15 *)
begin (* 16 *)
d := TJSDate.New; (* 17 *)
WriteLn(d.Year); (* 18 *)
d.Year := d.Year + 1; (* 19 *)
WriteLn(d.Year); (* 20 *)
end. (* 21 *)
在第 3 行至第 13 行間,我們定義了 TJSDate
類別,該類別來自 JavaScript 的 Date
物件。在撰寫 TJSDate
類別時,請參閱 JavaScript 的 Date
物件的 API 來寫。
結語
在本文中,我們展示了將 Pascal 程式碼轉為 JavaScript 程式碼的方式。若要熟練地使用 pas2js
,要對 Pascal 和 JavaScript 都有一定的熟悉度,寫起來不一定會比寫原生 JavaScript 程式來得簡單。
除了用 pas2js
撰寫新的網頁程式外,我們還可以透過 pas2js
把現有的 Pascal 程式移植到網頁平台上,讓原有的 Pascal 程式的生命週期延續下去。或許這才是 pas2js
的真正益處。