在 ToyLang 中,一個 .toy 檔案就是一個模組,載入模組的 .toy 檔案時,會執行整個 .toy 的內容,預設是不匯出任何東西,如果想要匯出變數、函式、類別之類,必須定義 exports
清單。
以 math.toy 為例:
import 'sys'
exports = ['min', 'max', 'abs', 'sum', 'Random', 'random', 'pow']
def min(numbers) {
return numsOrArgs(numbers, arguments).sort().get(0)
}
def max(numbers) {
return numsOrArgs(numbers, arguments).sort((n1, n2) -> n2 - n1).get(0)
}
def abs(n) {
return -n if n < 0 else n
}
def sum(numbers) {
return numsOrArgs(numbers, arguments).reduce((acc, n) -> acc + n, 0)
}
def numsOrArgs(nums, args) {
return nums if isInstance(nums, List) else args
}
modulus = 2147483647 # m
multiplier = 1103515245 # a
increment = 12345 # c
... 略
exports
清單中包含之名稱,才會被匯出,若其他模組使用 import
或 import as
,被匯出的名稱,就會成為 Module
實例上的特性,若是使用 from import
,只有 exports
中包含之名稱,才能成為 import
的對象,或者是使用 from '/lib/math' import *
,這會將 exports
清單中指定的全部名稱,都在環境物件中建立對應的變數,並將原模組中的值指定給該變數。
因為會在環境物件中建立對應的變數,並將原模組中的值指定給該變數,如果有個 lib/some.toy 寫這樣:
exports = ['X', 'FOO']
X = 10
FOO = [1, 2, 3]
那麼在另一個 main.toy 中寫這樣:
from '/lib/some' import *
X = 20
FOO.set(0, 10)
那麼,只是在 main
中的變數 X
被指定了新值,而不會影響 some
中 X
的值,然而,main
中的變數 FOO
與 some
中的 FOO
參考了同一物件,因此若程式其他部份,試著取得 some
模組的 FOO
,結果會得到 [10, 2, 3]
的清單。
若想知道目前已經載入了哪些模組,可以透過 sys
模組的 loadedModules
函式:
import '/lib/toy'
import '/lib/sys'
import '/lib/this'
println(sys.loadedModules())
exports
這特性,其實是學 Python 的,不過 Python 的模組預設是公開全部的名稱,我偏好想公開的再加進清單。
就如同〈import、import as、from import〉中談過,模組功能是最後才加上去的,模組不參與語法樹,import
、import as
、from import
其實也不一定要作為關鍵字,要實作為函式,甚至是清單,都是可以的,也就是說,想這麼使用 import
,也可以是選項之一:
import = ['sys', 'math']
回過頭來看 exports
,因為模組本身就是個 .toy 檔案,在執行完 .toy 之後,如果有 exports
,執行該模組時的環境物件中,就可以取得清單了,這時只要依清單中的名稱,取得對應的物件就可以了,這實作在 module.js 的 Module
之中:
...
play() {
const context = Context.initialize(environment, this);
this.importers.forEach(importer => importer.importTo(context));
// run module itself
const moduleContext = this.eval(context, this.parse());
const exportsValue = moduleContext.variables.get('exports');
const exports = new Set(exportsValue ? exportsValue.nativeValue().map(p => p.value) : []);
const exportVariables = new Map(
Array.from(moduleContext.variables.keys())
.filter(key => exports.has(key))
.map(key => [key, moduleContext.variables.get(key)])
);
// exports
const instance = moduleContext.variables.get('this');
instance.properties = exportVariables;
this.instance = instance;
return this.instance;
}
如果沒有 exports
清單的話,會有個空清單,當然,這時就什麼也沒有匯出了,在上頭也看到了 this
,在模組頂層,this
會參考至模組物件,也就是 ToyLang 中 Module
類別的實字。
在上頭也可以看到,實際上只是取得匯出的物件,讓執行 import
的模組之環境物件中之名稱,參考至那些物件,因此也只是傳值的行為。