內建函式與類別


在還沒撰寫任何函式或類別,也沒 import 任何模組之前,ToyLang 提供了一組內建函式與類別,這些內建函式與類別有的是以原生方式實作,有的定義在 builtin.toy 之中。

ToyLang 提供的內建函式有:

  • inputprintprintlnhasValuenoValuerangeiteratetypeofisInstance

ToyLang 提供的內建類別有:

  • ObjectModuleClassFunctionNumberStringListTraceableException

大部份的內建函式或類別,在之前的文件中都看過了,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