在還沒撰寫任何函式或類別,也沒 import
任何模組之前,ToyLang 提供了一組內建函式與類別,這些內建函式與類別有的是以原生方式實作,有的定義在 builtin.toy 之中。
ToyLang 提供的內建函式有:
input
、print
、println
、hasValue
、noValue
、range
、iterate
、typeof
、isInstance
ToyLang 提供的內建類別有:
Object
、Module
、Class
、Function
、Number
、String
、List
、Traceable
、Exception
大部份的內建函式或類別,在之前的文件中都看過了,Module
則代表模組,每個模組在匯入之後,都會有個 Module
實例作為代表,例如 builtin
:
# 顯示 <Module builtin>
println(builtin)
# 顯示上頭列出的內建函式與類別清單
println(builtin.ownProperties())
有些內建函式需要與環境溝通,例如 print
,實際上該輸出到哪,要看執行的環境,若是在瀏覽器,也許就是個 textarea
,因此這類函式以原生方式實作,這些原生函式實作在 functions.js:
function print(context, v) {
context.output(valueToString(context, v));
}
這是個 JavaScript 函式,要怎麼對應至 ToyLang 呢?ToyLang 實作時函式的語法樹節點是 Func
,只要能建立一個代表 print
函式的 Func
節點並加入語法樹中就可以了。
然而,在建立多個原生函式之後,在建立 Func
這方面會發現有許多重複的程式碼,最後這些被重構到 func_bases.js 之中:
const PARAM1 = Variable.of('p1');
const PARAM2 = Variable.of('p2');
const PARAM3 = Variable.of('p3');
const PARAM_LT0 = [];
const PARAM_LT1 = [PARAM1];
const PARAM_LT2 = [PARAM1, PARAM2];
const PARAM_LT3 = [PARAM1, PARAM2, PARAM3];
function func(name, node, params = PARAM_LT0) {
return new Func(params, node, name);
}
function func0(name, node) {
return func(name, node);
}
function func1(name, node) {
return func(name, node, PARAM_LT1);
}
... 略
這些就只是輔助函式罷了,因此,若要建立 print
原生函式:
function print(context, v) {
context.output(valueToString(context, v));
}
const Print = func1('print', {
evaluate(context) {
print(context, PARAM1.evaluate(context));
return context.returned(Void);
}
});
Print
就是語法樹節點了,然而,ToyLang 支援物件導向,每個函式是 ToyLang 中 Function
類別的實例,為此必須建立語法樹節點 Print
、代表 Function
實例的 Instance
等之關係:
const FUNC_CLZ = BUILTIN_CLASSES.get('Function');
function funcInstance(internalNode) {
return new Instance(FUNC_CLZ, new Map(), internalNode);
}
function funcEntry(name, internalNode) {
return [name, funcInstance(internalNode)];
}
... 略
const BUILTIN_FUNCTIONS = new Map([
funcEntry('input', Input),
funcEntry('print', Print),
funcEntry('hasValue', HasValue),
funcEntry('noValue', NoValue),
funcEntry('typeof', TypeOf),
funcEntry('nativeFunction', NativeFunction)
]);
BUILTIN_FUNCTIONS
的資料,最後會成為初始的環境物件中可查找的對象,以及 Module
實例中被 export
的對象。
至於內建類別,其實過程類似,BUILTIN_CLASSES
的組成是放在 classes.js:
const CLZ = ClassClass.classInstance(null, clzNode({name : 'Class', methods : ClassClass.methods}));
// 'Class' of is an instance of 'Class'
CLZ.clzOfLang = CLZ;
const BUILTIN_CLASSES = new Map([
ClassClass.classEntry(CLZ, 'Object', ObjectClass.methods),
ClassClass.classEntry(CLZ, 'Function', FunctionClass.methods),
['Class', CLZ],
ClassClass.classEntry(CLZ, 'Module', ModuleClass.methods),
ClassClass.classEntry(CLZ, 'String', StringClass.methods),
ClassClass.classEntry(CLZ, 'List', ListClass.methods),
ClassClass.classEntry(CLZ, 'Number', NumberClass.methods, NumberClass.constants),
ClassClass.classEntry(CLZ, 'Traceable', TraceableClass.methods)
]);
一些可共用的輔助函式,是放在 class_bases.js,這個就自己查看一下了,至於各個類別的實作,都放在 classes 之中,以 object.js 為例,當中實作了 Object
的原生方法定義:
class ObjectClass {}
ObjectClass.methods = new Map([
['init', func1('init', {
evaluate(context) {
const list = PARAM1.evaluate(context);
if(list !== Null) {
const instance = self(context);
list.nativeValue().forEach(prop => {
const plt = prop.nativeValue();
instance.setOwnProperty(plt[0].value, plt[1]);
});
}
return context;
}
})],
['ownProperties', func0('ownProperties', {
evaluate(context) {
const entries = Array.from(self(context).properties.entries())
.map(entry => ListClass.newInstance(context, [new Primitive(entry[0]), entry[1]]));
return context.returned(ListClass.newInstance(context, entries));
}
})],
['hasOwnProperty', func1('hasOwnProperty', {
evaluate(context) {
return context.returned(
Primitive.boolNode(self(context).hasOwnProperty(PARAM1.evaluate(context).value))
);
}
})],
['getOwnProperty', func1('getOwnProperty', {
evaluate(context) {
return context.returned(
self(context).getOwnProperty(PARAM1.evaluate(context).value)
);
}
})],
['setOwnProperty', func2('setOwnProperty', {
evaluate(context) {
const instance = self(context);
instance.setOwnProperty(PARAM1.evaluate(context).value, PARAM2.evaluate(context))
return context.returned(instance);
}
})],
['deleteOwnProperty', func1('deleteOwnProperty', {
evaluate(context) {
self(context).deleteOwnProperty(PARAM1.evaluate(context).value);
return context;
}
})],
['toString', func0('toString', {
evaluate(context) {
const clzNode = self(context).clzNodeOfLang();
return context.returned(new Primitive(`<${clzNode.name} object>`));
}
})],
['class', func0('class', {
evaluate(context) {
return context.returned(self(context).clzOfLang);
}
})],
['super', func3('super', {
evaluate(context) {
const parentClzNode = PARAM1.evaluate(context).internalNode;
const name = PARAM2.evaluate(context).value;
const args = PARAM3.evaluate(context);
const instance = self(context);
const clzNode = instance.clzNodeOfLang();
if(isSubType(context, clzNode, parentClzNode)) {
const func = parentClzNode.getOwnMethod(name);
return func.bodyStmt(context, args === Null ? [] : args.nativeValue())
.evaluate(context.assign('this', instance));
}
throw new ClassError('obj.super(parent): the type of obj must be a subtype of parent');
}
})]
]);
如果你知道怎麼實作原生函式,應該可以看懂上頭的原始碼。基本上,可以把很多函式都實作為原生函式,甚至全部對應到 JavaScript 標準程式庫都可以,不過這樣沒什麼意思,因此,我才將一些內建函式放到 builtin.toy 之中,正所謂自己的狗食自己吃…XD