在 ToyLang 中,區分基本型態與複合型態,基本型態有數值、布林與字串,複合型態就是常稱的物件,在這邊先談談數值與布林。
ToyLang 中的數值沒有整數與浮點數的區別,數值都是 IEEE 754 標準 64 位元浮點數,可表示的數字最小值為 Number.MIN_VALUE
,而最大值為 Number.MAX_VALUE
,可使用的安全整數範圍為 Number.MAX_SAFE_INTEGER
與 Number.MIN_SAFE_INTEGER
。
撰寫數值時要使用數值實字(Number literal),預設是十進位整數,也可以用 0x
開頭表示 16 進位整數、0o
表示 8 進位整數,使用 0b
表示 2 進位整數。例如:
- 10 (10 進位)
- 0xFF (16 進位)
- 0o77 ( 8 進位)
- 0b11 ( 2 進位)
若要表示浮點數,也可使用科學記號表示。例如:
- 3.14
- 5.231e13
- 1.31e-32
顯然地,Number
是個名稱空間,除了可以用來取得一些常數之外,還有 Number.parseInt
、Number.parseFloat
可以使用。
println(Number.parseInt('10')) # 顯示 10
println(Number.parseInt('10', 8)) # 顯示 8
println(Number.parseInt('10', 2)) # 顯示 2
println(Number.parseFloat('3.14e10')) # 顯示 31400000000
你看到的 #
符號是用來撰寫註解之用,可以放在單獨的一行,或者是單行程式碼的後頭。
布林值只有兩個值,true
與 false
,分別表示真與假。
為什麼 ToyLang 的數值是浮點數呢?我偷懶了,直接將數值對應至 JavaScript 的數值,而 JavaScript 的數值就是使用浮點數來表示。
如果要精實一些的話,是可以區分整數與浮點數,在開發一門語言時,對於每個「值」,必須有個語法節點,以 simple_lang.js 中的 Num
為例:
class Num {
constructor(value) {
this.value = value;
}
evaluate(context) {
return this;
}
}
基本型態的節點很單純,封裝了某個底層值,例如 new Num(1)
是用 Num
實例封裝了 JavaScript 的數值 1,new Num(3.14)
封裝了 JavaScript 的數值 3.14。
evaluate
是運行語法節點時會用到的方法,對基本型態來說,單純傳回物件本身,方法的呼叫者,就可以透過傳回值的 value
特性取得底層數值,之後的文件會談到運算式的組合,屆時就會看到 evaluate
的運用。
如果想要區分整數與浮點數的話,可以分別定義 Int
與 Float
節點:
class Int {
constructor(value) {
this.value = value;
}
evaluate(context) {
return this;
}
}
class Float {
constructor(value) {
this.value = value;
}
evaluate(context) {
return this;
}
}
這時 Int
只接受 1、2、3 這類的底層整數,Float
可接受 3.14 這類的浮點數,如果今天是使用一個強型別語言來實作 ToyLang,例如 Python,那麼區分整數與浮點數的動作,就會是必要的,因為在數值的自動型態轉換上受到嚴格的限制,Int
與 Float
在處理上必然不同。
然而,因為目前 ToyLang 使用 JavaScript 來實現,而 JavaScrip 的自動型態轉換是出了名的方便(或者說難搞),如上區分 Int
或 Float
意義上較小,因此我就合併,在 simple_lang.js 中是使用一個 Num
來代表數值。
後面的文件會看到,在 ToyLang 中,更進一步地,我將基本型態都使用 Primitive
來表示。
注意,這邊談到的 Num
、Int
、Float
等,並不是 ToyLang 中的類別,而只是實現語法節點時,原生語言用來表示語法節點的類別,要談到 ToyLang 中的類別,還有很長的一段路要走。
布林值也是類似的,只不過因為布林值只有 true
與 false
兩個值,因此在語法節點上,可以直接產生兩個物件,以 simple_lang.js 的做法來說..
const BOOL_TRUE = new Num(true)
const FALSE_TRUE = new Num(false)
實現語言必然要有語法剖析器的存在,簡單來說,任何程式碼對語言實作品來說,不過就是一個或長或短的字串,語法剖析器的目的,就是辨識字串,並將之轉換為正確的語法節點。
撰寫剖析器很麻煩,有些介紹實作語言的文件或書,都會使用現成的剖析器產生器,以便簡化,不過,這並不表示你不用知道剖析器的原理,或者不用思考文法等細節。
對我來說,認識如何語法剖析器基本上如何實現,也是課題之一,因此我自行實現了簡單的語法剖析器,大量地使用 Regular expression 進行字串的辨識,技巧上應該是很不成熟或欠缺效率,然而可以運作,也從中知道了許多剖析器設計上該注意的事。
就目前這文件的主題來說,要知道如何剖析出數值與布林值,布林值很簡單,只要比對 'true'
或 'false'
字串,因而 Regular expression 只要:
const BOOLEAN_REGEX = /true|false/;
對於數值來說就複雜一些了,最基本的是必須考慮 0 到 9 與小數點,也就是 /[0-9]+\.?[0-9]*/
,不過像 0x
、0o
、0b
也要考慮的話,就會是 /0[box][0-9A-F]+|[0-9]+\.?[0-9]*/
,最後考量科學記號,因此最後的 Regular expression 是:
const NUMBER_REGEX = /0[box][0-9A-F]+|[0-9]+\.?[0-9e+-]*/;
由於 ToyLang 大量使用 Regular expression 來進行比對,為了便於管理 Regular expression,絕大多數的 Regular expression 被集中在 regex.js 。
Regular expression 基本上不易閱讀也不易維護,因而可以的情況下,我會重用已經定義的 Regular expression,將之重組為更複雜的 Regular expression,這對剖析上沒有效率,然而對理解上有很大的幫助。