寫過這麼多語言的 Hello, World,終於有機會來為自己打造的語言寫個 Hello, World 介紹了,打開你的瀏覽器,連上 ToyLang 的示範網頁,在 Playground 中鍵入:
print('Hello, World')
接著按下「Run It」按鈕,就會顯示「Hello, World」了,夠簡單吧!有些開發者在宣傳他們熱愛的語言時,經常會示範該語言顯示 Hello, World 有多簡單,藉此顯示它的簡潔易用,按照這個邏輯,ToyLang 應該也是一門不錯的語言吧!
別再相信這種說法了,一門語言好不好用,跟 Hello, World 有什麼關係?…XD
如果你想要自己有個環境可以執行 ToyLang,只要下載 JustinSDK/toy_lang,將之放在一個簡單的 HTTP 伺服器上(像是 Node.js 的 http-server),使用支援 ECMAScript 6 的瀏覽器(目前主流常青瀏覽器都支援)瀏覽 index.html,就可以開始使用了。
這是因為 ToyLang 是使用 ECMAScript 6 來實現的語言,不依賴任何第三方程式庫,如果你也好奇一門語言是如何打造出來的,或許 ToyLang 可以成為一個不錯的入門對象。
雖然打造 ToyLang 過程中,主要都是在瀏覽器的環境中測試,然而,語言的內部實現都是 ECMAScript 6 標準 API,沒有使用到瀏覽器特定 API,因而想要在本機上執行 ToyLang,只要你有個支援 ECMAScript 6 模組的環境,基本上是沒有問題的。
例如,Node.js 可以打開 --experimental-modules
,然後使用 Michael Jackson 模式,也就是將 .js 都改成 .mjs,就可以支援 ECMAScript 6 模組,若想要執行 ToyLang,必須有個讀取程式進入點檔案的程式,這可以參考 index.html 中底下的原始碼:
import {Module} from './toy_lang/js/module.js';
const TOY_MODUEL_PATH = 'toy_lang';
const ENVIRONMENT = {
input(message) {
return prompt(message, '');
},
output(value) {
tty.textContent += value;
},
TOY_MODUEL_PATH,
readFile : function(fileName) {
return fetch(fileName)
.then(resp => {
if(resp.ok) {
return resp.text();
}
throw new Error(`GET ${fileName} ${resp.status} (${resp.statusText})`);
})
.then(code => [fileName, code]);
}
};
Module.initialize(ENVIRONMENT)
.then(_ => playToy());
function playToy() {
document.getElementById('run').addEventListener('click', _ => {
let code = document.getElementById('code').value;
let tty = document.getElementById('tty');
tty.textContent = '';
Module.run('/main.toy', code);
});
}
在瀏覽器上,讀取使用者輸入使用了 prompt
,輸出目的地是個 textarea
,如果要讀取檔案,使用了 Fetch,記得它會傳回一個 Promise
,當中有讀取的檔案名稱及程式碼,然而這些都被封裝至一個 ENVIRONMENT
。
TOY_MODUEL_PATH
是 .toy 模組檔案的路徑設定,如果在 Toy 原始碼中 import '/xxx/ooo'
,也就是路徑以 /
開頭,表示採絕對路徑,這個絕對路徑實際上是以 TOY_MODUEL_PATH
起算,目前是指向 toylang
。
如果想要在 Node.js 上執行,只要將 input
、output
、readFile
使用 Node.js 的 API 實現,透過 Module.initialize(ENVIRONMENT)
初始化,之後進行程式進入點的讀取,然後將讀取的檔名與程式碼,傳給 Module.run
就可以了。
平均來說,ToyLang 在實現時,每個模組控制在不超過 300 行程式碼,通常只有 100 行左右,平均一個模組檔案有 150 行的話,目前有約 40 來個檔案,因而程式碼總行數約在 4、5000 行左右,是個語言實作的小專案。
如果你想要知道更基本的語言實作方式,那就參考 simple_lang.js 吧!
因為當初只是想寫個簡單的語言,有個簡單的運算式、流程語法,體驗一下怎麼實作就好,就只發佈為 Gist,然而…流程語法玩出來後,就說要不加個函式功能好了…函式動起來之後,覺得搞不好可以玩 Closure,Closure 行了就又想試類別,類別完成接著認為繼承應該也 OK,繼承妥善後就又心生挑戰模組了,就這麼一路玩了下來…XD
你可以在 simple_lang.js 中看到,最初 print
並不是個函式,而是個陳述句(就像 Python 2.x 中 print
是個陳述句,Python 3.0 之後是個函式),如果想要入門語言實作,將 print
實現為陳述句會是個比較簡單的作法,等到函式的基礎設施都實現之後,再將之改為函式。
在實現語言的過程中,主要參考的語法對象是 Python,因為 Python 的語法對 Parser 實現來說非常友善,至於行為上,主要參考的對象是 JavaScript、Python,以及一點點的 Java 及 Ruby(simple_lang.js 中的 end
就是學 Ruby 的),因而在 ToyLang 上看到 Python、JavaScript、Java 或 Ruby 的影子並不意外。
在後續的文件中,我會一邊解釋 ToyLang 的語法與行為,一邊解釋語言內部在實現上是採取什麼樣的策略,若你知道編譯原理等相關知識,在後續覺得 ToyLang 的做法土炮而陽春的話,那很正常,因為我是打著先動手做、碰上問題、解決問題,以便後續在看編譯原理的書或文件時,知道它們在談論些什麼的方式。
打造語言一直是我想要嘗試的,只是過去總覺得拼圖裡少了那麼幾塊,令我覺得拼圖湊得差不多的一本書是《深入理解運算原理》,你也可以試著閱讀,在讀完這本書之後,我也寫了〈運算隨想〉記錄個人心得,可以搭配服用!