內建函式與類別

December 20, 2021

在還沒撰寫任何函式或類別,也沒 import 任何模組之前,Toy Lang 提供了一組內建函式與類別。

Toy 語法

這些內建函式與類別有的是以原生方式實作,有的定義在 builtin.toy 之中。

Toy Lang 提供的內建函式有:

  • inputprintprintlnhasValuenoValuerangeiteratetypeofisInstance

Toy Lang 提供的內建類別有:

  • ObjectModuleClassFunctionNumberStringListTraceableException

大部份的內建函式或類別,在之前的文件中都看過了,Module 則代表模組,每個模組在匯入之後,都會有個 Module 實例作為代表,例如 builtin

# 顯示 <Module builtin>
println(builtin)

# 顯示上頭列出的內建函式與類別清單
println(builtin.ownProperties())

Toy 實作

有些內建函式需要與環境溝通,例如 print,實際上該輸出到哪,要看執行的環境,若是在瀏覽器,也許就是個 textarea,因此這類函式以原生方式實作,這些原生函式實作在 functions.js

function print(context, v) {
	context.output(valueToString(context, v));
}

這是個 JavaScript 函式,要怎麼對應至 Toy Lang 呢?Toy Lang 實作時函式的語法樹節點是 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 就是語法樹節點了,然而,Toy Lang 支援物件導向,每個函式是 Toy Lang 中 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