# 定義類別

December 20, 2021

## Toy 語法

``````class Account {
balance = 0

def init(number, name) {
this.number = number
this.name = name
}

def deposit(amount) {
if amount <= 0 {
throw new Exception('must be positive')
}

this.balance += amount
}

def withdraw(amount) {
if amount > this.balance {
throw new Exception('balance not enough')
}

this.balance -= amount
}

def toString() {
return '{0}, {1}, {2}'.format(this.number, this.name, this.balance)
}
}

acct = new Account('123', 'Justin')
acct.deposit(100)
acct.withdraw(20)
println(acct)
``````

`init` 方法是個特定的名稱，用來定義類別的實例建立之後，要進行的初始化動作，如果要建立類別的實例，可以使用 `new` 關鍵字，例如 `new Account('123', 'Justin')`，這會執行類別本體陳述，接著建立一個物件，將類別本體陳述中建立的變數，指定為物件上之特性，之後物件會成為方法中 `this` 參考之對象，呼叫 `init` 方法，`'123'` 會指定給 `number` 參數，而 `'Justin'` 指定給 `name` 參數，然後執行 `init` 本體完成初始化。

``````println(acct.name)                   # 顯示 Justin
println(acct.hasOwnProperty('name')) # 顯示 true
``````

``````println(acct.deposit)                    # 顯示 <Function deposit>
println(acct.hasOwnProperty('deposit'))  # 顯示 false
println(Account.hasOwnMethod('deposit')) # 顯示 true
``````

## Toy 實作

``````def acct_init(number, name) {
return [number, name, 0]
}

def acct_deposit(acct, amount) {
if amount <= 0 {
throw new Exception('must be positive')
}
balance = acct.get(2)
acct.set(2, balance + amount)
}

def acct_withdraw(acct, amount) {
if amount > acct.get(2) {
throw new Exception('balance not enough')
}
balance = acct.get(2)
acct.set(2, balance - amount)
}

def acct_toString(acct) {
return '{0}, {1}, {2}'.format(acct.get(0), acct.get(1), acct.get(2))
}

acct = acct_init('123', 'Justin')
acct_deposit(acct, 100)
acct_withdraw(acct, 20)
println(acct_toString(acct))
``````

``````function createAssignFunc(tokenableLines, argTokenable) {
const [fNameTokenable, ...paramTokenables] = argTokenable.tryTokenables('func');
fNameTokenable.errIfKeyword();

const remains = tokenableLines.slice(1);
const bodyStmt = LINE_PARSER.parse(remains);
const bodyLineCount = bodyStmt.lineCount;

return new StmtSequence(
new DefStmt(
Variable.of(fNameTokenable.value),
new Func(
paramTokenables.map(paramTokenable => Variable.of(paramTokenable.value)),
bodyStmt,
fNameTokenable.value
)
),
LINE_PARSER.parse(tokenableLines.slice(bodyLineCount + 2)),
tokenableLines[0].lineNumber
);
}

function createAssignClass(tokenableLines, argTokenable) {
const [fNameTokenable, ...paramTokenables] = argTokenable.tryTokenables('func');
fNameTokenable.errIfKeyword();

const remains = tokenableLines.slice(1);
const stmt = LINE_PARSER.parse(remains);
const clzLineCount = stmt.lineCount + 2;

const parentClzNames = paramTokenables.map(paramTokenable => paramTokenable.value);
const [fs, notDefStmt] = splitFuncStmt(stmt);

return new StmtSequence(
new ClassStmt(
Variable.of(fNameTokenable.value),
new Class({
notMethodStmt : notDefStmt,
methods : new Map(fs),
name : fNameTokenable.value,
parentClzNames : parentClzNames.length === 0 ? ['Object'] : parentClzNames
})
),
LINE_PARSER.parse(tokenableLines.slice(clzLineCount)),
tokenableLines[0].lineNumber
);
}
``````

``````class Class extends Func {
constructor({notMethodStmt, methods, name, parentClzNames, parentContext}) {
super([], notMethodStmt, name, parentContext || null);
this.parentClzNames = parentClzNames || ['Object'];
this.methods = methods;
}

...

hasOwnMethod(name) {
return this.methods.has(name);
}

...

}
``````

`Class` 節點定義在 value.js 中；除了剖析時有很大部份與函式類似，執行面上也有大部份是雷同，因此 `Class` 節點繼承了 `Func` 節點，非 `def` 陳述的部份，使用 `super` 交給了 `Func` 建構式，至於 `def` 陳述部份，由 `Class` 節點本身來管理，像是判斷類別有無定義某個方法，就實現為 `hasOwnMethod`

``````class NewOperator {
constructor(operand) {
this.operand = operand;
}

instance(context, args) {
const clzInstance = clzInstanceFrom(context, this.operand);
// run class body
const ctx = clzInstance.internalNode.call(context, args);
return ctx.notThrown(c => {
c.variables.delete('arguments');
return new Instance(
clzInstance,
c.variables
);
});
}

evaluate(context) {
const args = argsFrom(this.operand);
const maybeContext = this.instance(context, args);
return maybeContext.notThrown(ctx => {
if(ctx.clzNodeOfLang().hasOwnMethod('init')) {
const maybeCtx = new MethodCall(maybeContext, 'init', [args]).evaluate(context);
return maybeCtx.notThrown(c => maybeContext);
}
return ctx;
});
}
}
``````

`instance` 方法就可以看到先執行類別本體，取得環境物件上的變數並建立 `Instance` 節點的動作，執行類別本體本質上就是呼叫函式，因而會有個 `arguments`，這對類別的實例來說，並非需要的特性，因此將之刪除。

`instance` 方法過後，就是看看類別本身是否定義了 `init`，若有才會執行，也就是建立一個 `MethodCall` 節點並執行，這實現在 callable.js 中：

``````class MethodCall {
constructor(instance, methodName, argsList = []) {
this.instance = instance;
this.methodName = methodName;
this.argsList = argsList;
}

evaluate(context) {
return methodBodyStmt(context, this.instance, this.methodName, this.argsList[0])
.evaluate(methodContextFrom(context, this.instance, this.methodName))
.notThrown(c => {
if(this.argsList.length > 1) {
return callChain(context, c.returnedValue.internalNode, this.argsList.slice(1));
}
return c.returnedValue === null ? Void : c.returnedValue;
});
}
}

function methodBodyStmt(context, instance, methodName, args = []) {
const f = instance.hasOwnProperty(methodName) ?
instance.getOwnProperty(methodName).internalNode :
instance.clzNodeOfLang().getMethod(context, methodName);
const bodyStmt = f.bodyStmt(context, args.map(arg => arg.evaluate(context)));
return new StmtSequence(
new VariableAssign(Variable.of('this'), instance),
bodyStmt,
bodyStmt.lineNumber
);
}
``````

``````class Instance extends Value {
constructor(clzOfLang, properties, internalNode) {
super();
this.clzOfLang = clzOfLang;
this.properties = properties;
this.internalNode = internalNode || this;
this.value = this;
}

clzNodeOfLang() {
return this.clzOfLang.internalNode;
}

nativeValue() {
return this.internalNode.value;
}

hasOwnProperty(name) {
return this.properties.has(name);
}

...
}
``````

`properties` 參數接受的就是 `Map`，另一個重要的部份，就是 `clzOfLang` 了，每個實例必須知道它是屬於哪個類別，如此在使用 `.` 運算子時，才會知道要到哪個類別上尋找是否有定義方法。

`.` 運算子的部份，就等到談 `this` 的細節時再來討論了；實際上，類別在處理上有很多的細節，這邊談到的節點，其實也都省略了不少程式碼，主要是先知道有這些節點的存在，以及它們各自在哪些地方，之後有機會，也會來看看那些被省略的程式碼，到底各自負責了什麼。