如何使用 JavaScript 實現一門程式語言(1) : 前言

發表於2018-03-27

這是一系列關於 如何實現程式語言 的教程。如果你曾經寫過一個直譯器或編譯器,那麼這裡可能沒有什麼新東西。但是,如果您使用正規表示式來“解析” 任何看起來像程式語言的東西,那麼請至少閱讀解析部分。讓我們寫出更少的錯誤程式碼!

目標受眾是普通的 JavaScript / NodeJS 程式設計師。

我們要學什麼?

  • 什麼是解析器,以及如何編寫解析器。
  • 如何編寫直譯器。
  • 為什麼它們很重要。
  • 編寫一個編譯器。
  • 如何將程式碼轉換為延續傳遞樣式。
  • 一些基本的優化技術。

在兩者之間,我會爭論為什麼 Lisp 是一種優秀的程式語言。 但是,我們將要使用的語言不是 Lisp。它有一個更豐富的語法(每個人都知道的經典中綴符號),除巨集之外,它的功能與 Scheme相當。
可悲的是,巨集是 Lisp 的最終堡壘,其他語言無法克服(除非它們被稱為 Lisp 方言)。

首先,讓我們想想我們的要實現的程式語言應該是什麼樣子。

我們應該想清楚自己想要實現的目標。把語法的嚴格描述放在一起是一個好主意,但是我會在本教程中使語法更加簡單,下面的示例就是我們要實現 “λ” 語言:

請注意,識別符號名稱可以包含負號字元(print-range)。這是個人品味的問題:我總是在操作符旁邊放置空格,我不喜歡太多的 camelCaseNames,而且我覺得短劃線比下劃線更好。編寫自己的語言的好處是,你可以隨心所欲地做到這一點。:)

輸出是:

該語言看起來有點像 JavaScript,但它不同。首先,沒有宣告,只有表示式。表示式返回一個值,可以用來代替任何其他表示式。分號需要在“序列”中分隔表示式。花括號{和}建立這樣一個序列,它本身就是一個表示式。它的值是最後一個表示式返回的值。以下是一個有效的程式:

函式被引入其中一個關鍵字 lambda 或 λ(它們是同義詞)。在關鍵字之後,必須有一個(可能為空的)用逗號分隔的變數名列表(可能為空),就像在 JavaScript 中一樣 —— 這些是引數名稱。函式體是一個單一的表示式,但它可以是一個包裹在{ … }中的序列。
沒有 return 語句 – 在函式返回最後一個表示式給出的返回值。

沒有 var。要引入新變數,可以使用 JSer 稱之為 “立即執行函式” 的方式。就像在 JavaScript 中一樣,使用 a lambda,宣告變數作為引數。變數具有函式作用域範圍,函式是閉包。

甚至 if 本身就是一種表達。在 JavaScript 中,您可以使用三元運算子獲得該效果:

當分支以一個花括號開始時, then 關鍵字是可選,你可以在 print-range 上面看到,否則它是必需的。
當替代分支存在時,else 關鍵字是必須的。再次,then 的 else 的分支主題為一個單一的表示式,但也可以花括號 “{}” 包含多個通過 “;” 分隔的表示式。
當 else 分支不存在並且 if 條件結果為 false,if 表示式的結果是 false。因此,false 是一個關鍵詞,它表示我們的語言中唯一的 falsy 值:

當 foo() 的結果不是 false 是,這點程式將會列印 “OK”。還有一個用於表示”真”的關鍵字 true,不是所非 false(當使用 JavaScript中 === 運算子的時候)的值都被理解成 true (其中包括數字 0 和空字串 “”)。

還要注意,使用括號包裹 if 條件沒有意義。不過,雖然它們是多餘的,你新增它們也沒有錯誤。

整個程式被解析,就好像它被嵌入大括號中一樣,因此,除了最後一個表示式,您需要在每個表示式後面放置一個分號。

好了,這是就我們的小 λ 語言。它不一定是很完善的的。它的語法看起來很可愛,但也有陷阱。它有很多缺失的功能,如物件或陣列; 我們並不關注這些缺失,因為對我們的旅程並不重要。只要你掌握了這個教程的所有內容,你可以輕鬆實現這些缺失的功能。

在下一節中, 我們將為這個語言編寫一個解析器 。

相關文章