博客前言
本篇博客是接着上一次博客的,上一篇博客编译了类中变量方法,但是这只是代码定义类而已,并没有实际使用(创建类的实例使用类的成员变量和方法等),本篇博客就是列举了当编译实际使用变量调用方法语句时,函数的执行顺序,这与上篇博客密切相关,因为上篇博客完成的编译类中变量方法等,实际是在编译单元cu->LocalVar、vm->allMethodNames、classBK->fields等结构中完成声明以及部分还需要赋值的操作,而本篇博客的是依靠方法、变量标识符来加载使用或者修改他们,所以也需要频繁访问上述在上篇博客用到的结构。
本章核心的两个函数就是expression使用id.nud,每个标识符都需要使用expression计算,expression识别出curtoken的type为id后,调用id.nud来生成变量方法对应的指令,变量即生成的是加载或存储指令、方法则生成的是调用指令。
1. 编译语句: 2+3
//数字和字符串.nud() 编译字面量
static void literal(CompileUnit* cu, bool canAssign UNUSED) {
//literal是常量(数字和字符串)的nud方法,用来返回字面值.
emitLoadConstant(cu, cu->curParser->preToken.value);
}
//生成加载常量的指令
static void emitLoadConstant(CompileUnit* cu, Value value) {
int index = addConstant(cu, value);
writeOpCodeShortOperand(cu, OPCODE_LOAD_CONSTANT, index);
}
CASE(STORE_LOCAL_VAR):
//栈顶: 局部变量值
//指令流: 1字节的局部变量索引
//将PEEK()得到的栈顶数据写入指令参数(即READ_BYTE()得到的值)为索引的栈的slot中
stackStart[READ_BYTE()] = PEEK();
LOOP();
CASE(LOAD_CONSTANT):
//指令流: 2字节的常量索引
//加载常量就是把常量表中的数据入栈
PUSH(fn->constants.datas[READ_SHORT()]);
LOOP();
//往函数的指令流中写入1字节,返回其索引
static int writeByte(CompileUnit* cu, int byte) {
//若在调试状态,额外在debug->lineNo中写入当前token行号
#if DEBUG
IntBufferAdd(cu->curParser->vm,
&cu->fn->debug->lineNo, cu->curParser->preToken.lineNo);
#endif
ByteBufferAdd(cu->curParser->vm,
&cu->fn->instrStream, (uint8_t)byte);
return cu->fn->instrStream.count - 1;
}
//不关注左操作数的符号称为前缀符号
//用于如字面量,变量名,前缀符号等非运算符
#define PREFIX_SYMBOL(nud) {NULL, BP_NONE, nud, NULL, NULL}
//前缀运算符,如'!'
#define PREFIX_OPERATOR(id) {id, BP_NONE, unaryOperator, NULL, unaryMethodSignature}
//关注左操作数的符号称为中缀符号
//数组'[',函数'(',实例与方法之间的'.'等
#define INFIX_SYMBOL(lbp, led) {NULL, lbp, NULL, led, NULL}
//中棳运算符
#define INFIX_OPERATOR(id, lbp) {id, lbp, NULL, infixOperator, infixMethodSignature}
//既可做前缀又可做中缀的运算符,如'-'
#define MIX_OPERATOR(id) {id, BP_TERM, unaryOperator, infixOperator, mixMethodSignature}
//占位用的
#define UNUSED_RULE {NULL, BP_NONE, NULL, NULL, NULL}
typedef struct {
const char* id; //符号
//左绑定权值,不关注左边操作数的符号此值为0
BindPower lbp;
//字面量,变量,前缀运算符等不关注左操作数的Token调用的方法
DenotationFn nud;
//中缀运算符等关注左操作数的Token调用的方法
DenotationFn led;
//表示本符号在类中被视为一个方法,
//为其生成一个方法签名.
methodSignatureFn methodSign;
} SymbolBindRule; //符号绑定规则
SymbolBindRule Rules[] = {
/* TOKEN_INVALID*/ UNUSED_RULE,
/* TOKEN_NUM */ PREFIX_SYMBOL(literal),
/* TOKEN_STRING */ PREFIX_SYMBOL(literal),
/* TOKEN_ID */ {NULL, BP_NONE, id, NULL, idMethodSignature},
/* TOKEN_INTERPOLATION */ PREFIX_SYMBOL(stringInterpolation),
/* TOKEN_VAR */ UNUSED_RULE,
/* TOKEN_FUN */ UNUSED_RULE,
/* TOKEN_IF */ UNUSED_RULE,
/* TOKEN_ELSE */ UNUSED_RULE,
/* TOKEN_TRUE */ PREFIX_SYMBOL(boolean),
/* TOKEN_FALSE */ PREFIX_SYMBOL(boolean),
/* TOKEN_WHILE */ UNUSED_RULE,
/* TOKEN_FOR */ UNUSED_RULE,
/* TOKEN_BREAK */ UNUSED_RULE,
/* TOKEN_CONTINUE */ UNUSED_RULE,
/* TOKEN_RETURN */ UNUSED_RULE,
/* TOKEN_NULL */ PREFIX_SYMBOL(null),
/* TOKEN_CLASS */ UNUSED_RULE,
/* TOKEN_THIS */ PREFIX_SYMBOL(this),
/* TOKEN_STATIC */ UNUSED_RULE,
/* TOKEN_IS */ INFIX_OPERATOR("is", BP_IS),
/* TOKEN_SUPER */ PREFIX_SYMBOL(super),
/* TOKEN_IMPORT */ UNUSED_RULE,
/* TOKEN_COMMA */ UNUSED_RULE,
/* TOKEN_COMMA */ UNUSED_RULE,
/* TOKEN_LEFT_PAREN */ PREFIX_SYMBOL(parentheses),
/* TOKEN_RIGHT_PAREN */ UNUSED_RULE,
/* TOKEN_LEFT_BRACKET */ {NULL, BP_CALL, listLiteral, subscript, subscriptMethodSignature},
/* TOKEN_RIGHT_BRACKET */ UNUSED_RULE,
/* TOKEN_LEFT_BRACE */ PREFIX_SYMBOL(mapLiteral),
/* TOKEN_RIGHT_BRACE */ UNUSED_RULE,
/* TOKEN_DOT */ INFIX_SYMBOL(BP_CALL, callEntry),
/* TOKEN_DOT_DOT */ INFIX_OPERATOR("..", BP_RANGE),
/* TOKEN_ADD */ INFIX_OPERATOR("+", BP_TERM),
/* TOKEN_SUB */ MIX_OPERATOR("-"),
/* TOKEN_MUL */ INFIX_OPERATOR("*", BP_FACTOR),
/* TOKEN_DIV */ INFIX_OPERATOR("/", BP_FACTOR),
/* TOKEN_MOD */ INFIX_OPERATOR("%", BP_FACTOR),
/* TOKEN_ASSIGN */ UNUSED_RULE,
/* TOKEN_BIT_AND */ INFIX_OPERATOR("&", BP_BIT_AND),
/* TOKEN_BIT_OR */ INFIX_OPERATOR("|", BP_BIT_OR),
/* TOKEN_BIT_NOT */ PREFIX_OPERATOR("~"),
/* TOKEN_BIT_SHIFT_RIGHT */ INFIX_OPERATOR(">>", BP_BIT_SHIFT),
/* TOKEN_BIT_SHIFT_LEFT */ INFIX_OPERATOR("<<", BP_BIT_SHIFT),
/* TOKEN_LOGIC_AND */ INFIX_SYMBOL(BP_LOGIC_AND, logicAnd),
/* TOKEN_LOGIC_OR */ INFIX_SYMBOL(BP_LOGIC_OR, logicOr),
/* TOKEN_LOGIC_NOT */ PREFIX_OPERATOR("!"),
/* TOKEN_EQUAL */ INFIX_OPERATOR("==", BP_EQUAL),
/* TOKEN_NOT_EQUAL */ INFIX_OPERATOR("!=", BP_EQUAL),
/* TOKEN_GREATE */ INFIX_OPERATOR(">", BP_CMP),
/* TOKEN_GREATE_EQUAL */ INFIX_OPERATOR(">=", BP_CMP),
/* TOKEN_LESS */ INFIX_OPERATOR("<", BP_CMP),
/* TOKEN_LESS_EQUAL */ INFIX_OPERATOR("<=", BP_CMP),
/* TOKEN_QUESTION */ INFIX_SYMBOL(BP_ASSIGN, condition),
/* TOKEN_EOF */ UNUSED_RULE
};
//语法分析的核心
static void expression(CompileUnit* cu, BindPower rbp) {
//以中缀运算符表达式"aSwTe"为例,
//大写字符表示运算符,小写字符表示操作数
//进入expression时,curToken是操作数w, preToken是运算符S
DenotationFn nud = Rules[cu->curParser->curToken.type].nud;
//表达式开头的要么是操作数要么是前缀运算符,必然有nud方法
ASSERT(nud != NULL, "nud is NULL!");
getNextToken(cu->curParser); //执行后curToken为运算符T
bool canAssign = rbp < BP_ASSIGN;
nud(cu, canAssign); //计算操作数w的值
while (rbp < Rules[cu->curParser->curToken.type].lbp) {
DenotationFn led = Rules[cu->curParser->curToken.type].led;
getNextToken(cu->curParser); //执行后curToken为操作数e
led(cu, canAssign); //计算运算符T.led方法
}
}
//中缀运算符.led方法
static void infixOperator(CompileUnit* cu, bool canAssign UNUSED) {
SymbolBindRule* rule = &Rules[cu->curParser->preToken.type];
//中缀运算符对左右操作数的绑定权值一样
BindPower rbp = rule->lbp;
expression(cu, rbp); //解析右操作数
//生成1个参数的签名
Signature sign = {SIGN_METHOD, rule->id, strlen(rule->id), 1};
emitCallBySignature(cu, &sign, OPCODE_CALL0);
}
//通过签名编译方法调用,包括callX和superX指令
static void emitCallBySignature(CompileUnit* cu, Signature* sign, OpCode opcode) {
char signBuffer[MAX_SIGN_LEN];
uint32_t length = sign2String(sign, signBuffer);
//确保签名录入到vm->allMethodNames中
int symbolIndex = ensureSymbolExist(cu->curParser->vm,
&cu->curParser->vm->allMethodNames, signBuffer, length);
writeOpCodeShortOperand(cu, opcode + sign->argNum, symbolIndex);
//此时在常量表中预创建一个空slot占位,将来绑定方法时再装入基类
if (opcode == OPCODE_SUPER0) {
writeShortOperand(cu, addConstant(cu, VT_TO_VALUE(VT_NULL)));
}
}
expression语句2+3时,
- 首先curtoken为2,识别到curtoken.type为num,故num的nud方法为literal,literal方法将该number存入常量表并生成了将常量从常量表加载至栈的指令。
- 然后curtoken变成+,然后执行while判断,+的绑定能力显然大于0,所以进入while循环体。
- 由Rules得知,+的led方法是infixOperator,infixOperator中将+的绑定权值传入expression,递归执行expression,同时此时的curtoken已经成为了3。
- 执行expression时,curtoken为3,3的nud方法仍为literal,同时3的绑定权值小于+的绑定权值,所以不会进入while循环体,故这层expression退出,回到+的led中。
- +的led,infixOperator执行完expression后,生成了根据加法签名生成了调用加法方法指令。
- +的led结束后,再次判断while循环条件,curtoken为3,3的绑定权值为0等于最初始的rbp,所以退出while循环。
总结:expression语句2+3生成了加载常量2、3入栈指令以及调用加法指令于栈顶
2.编译标识符:id,标识符的nud方法
//返回包含cu->enclosingClassBK的最近的CompileUnit
static CompileUnit* getEnclosingClassBKUnit(CompileUnit* cu) {
while (cu != NULL) {
if (cu->enclosingClassBK != NULL) {
return cu;
}
cu = cu->enclosingUnit;
}
return NULL;
}
//返回包含cu最近的ClassBookKeep
static ClassBookKeep* getEnclosingClassBK(CompileUnit* cu) {
CompileUnit* ncu = getEnclosingClassBKUnit(cu);
if (ncu != NULL) {
return ncu->enclosingClassBK;
}
return NULL;
}
//标识符.nud():变量名或方法名
static void id(CompileUnit* cu, bool canAssign) {
//备份变量名
Token name = cu->curParser->preToken;
ClassBookKeep* classBK = getEnclosingClassBK(cu);
//标识符可以是任意符号,按照此顺序处理:
//函数调用->局部变量和upvalue->实例域->静态域->类getter方法调用->模块变量
//处理函数调用
if (cu->enclosingUnit == NULL && matchToken(cu->curParser, TOKEN_LEFT_PAREN)) {
char id[MAX_ID_LEN] = {'\0'};
//函数名加上"Fn "前缀做为模块变量名,
//检查前面是否已有此函数的定义
memmove(id, "Fn ", 3);
memmove(id + 3, name.start, name.length);
Variable var;
var.scopeType = VAR_SCOPE_MODULE;
var.index = getIndexFromSymbolTable(
&cu->curParser->curModule->moduleVarName, id, strlen(id));
if (var.index == -1) {
memmove(id, name.start, name.length);
id[name.length] = '\0';
COMPILE_ERROR(cu->curParser, "Undefined function: '%s'!", id);
}
// 1 把模块变量即函数闭包加载到栈
emitLoadVariable(cu, var);
Signature sign;
//函数调用的形式和method类似,
//只不过method有一个可选的块参数
sign.type = SIGN_METHOD;
//把函数调用编译为"闭包.call"的形式,故name为call
sign.name = "call";
sign.length = 4;
sign.argNum = 0;
//若后面不是')',说明有参数列表
if (!matchToken(cu->curParser, TOKEN_RIGHT_PAREN)) {
// 2 压入实参
processArgList(cu, &sign);
consumeCurToken(cu->curParser, TOKEN_RIGHT_PAREN,
"expect ')' after argument list!");
}
// 3 生成调用指令以调用函数
emitCallBySignature(cu, &sign, OPCODE_CALL0);
} else { //否则按照各种变量来处理
//按照局部变量和upvalue来处理
Variable var = getVarFromLocalOrUpvalue(cu,
name.start, name.length);
if (var.index != -1) {
emitLoadOrStoreVariable(cu, canAssign, var);
return;
}
//按照实例域来处理
if (classBK != NULL) {
int fieldIndex = getIndexFromSymbolTable(&classBK->fields,
name.start, name.length);
if (fieldIndex != -1) {
if (classBK->inStatic) {
COMPILE_ERROR(cu->curParser,
"instance field should not be used in static method!");
}
bool isRead = true;
if (canAssign && matchToken(cu->curParser, TOKEN_ASSIGN)) {
isRead = false;
expression(cu, BP_LOWEST);
}
//如果当前正在编译类方法,则直接在该实例对象中加载field
if (cu->enclosingUnit != NULL) {
writeOpCodeByteOperand(cu,
isRead ? OPCODE_LOAD_THIS_FIELD : OPCODE_STORE_THIS_FIELD, fieldIndex);
} else {
emitLoadThis(cu);
writeOpCodeByteOperand(cu,
isRead ? OPCODE_LOAD_FIELD : OPCODE_STORE_FIELD, fieldIndex);
}
return;
}
}
//按照静态域查找
if (classBK != NULL) {
char* staticFieldId = ALLOCATE_ARRAY(cu->curParser->vm, char, MAX_ID_LEN);
memset(staticFieldId, 0, MAX_ID_LEN);
uint32_t staticFieldIdLen;
char* clsName = classBK->name->value.start;
uint32_t clsLen = classBK->name->value.length;
//各类中静态域的名称以"Cls类名 静态域名"来命名
memmove(staticFieldId, "Cls", 3);
memmove(staticFieldId + 3, clsName, clsLen);
memmove(staticFieldId + 3 + clsLen, " ", 1);
const char* tkName = name.start;
uint32_t tkLen = name.length;
memmove(staticFieldId + 4 + clsLen, tkName, tkLen);
staticFieldIdLen = strlen(staticFieldId);
var = getVarFromLocalOrUpvalue(cu, staticFieldId, staticFieldIdLen);
DEALLOCATE_ARRAY(cu->curParser->vm, staticFieldId, MAX_ID_LEN);
if (var.index != -1) {
emitLoadOrStoreVariable(cu, canAssign, var);
return;
}
}
//如果以上未找到同名变量,有可能该标识符是同类中的其它方法调用
//方法规定以小写字符开头
if (classBK != NULL && isLocalName(name.start)) {
emitLoadThis(cu); //确保args[0]是this对象,以便查找到方法
//因为类可能尚未编译完,未统计完所有方法,
//故此时无法判断方法是否为未定义,留待运行时检测
emitMethodCall(cu, name.start,
name.length, OPCODE_CALL0, canAssign);
return;
}
//按照模块变量处理
var.scopeType = VAR_SCOPE_MODULE;
var.index = getIndexFromSymbolTable(
&cu->curParser->curModule->moduleVarName, name.start, name.length);
if (var.index == -1) {
//模块变量属于模块作用域,若当前引用处之前未定义该模块变量,
//说不定在后面有其定义,因此暂时先声明它,待模块统计完后再检查
//用关键字'fun'定义的函数是以前缀"Fn "后接"函数名"做为模块变量
//下面加上"Fn "前缀按照函数名重新查找
char fnName[MAX_SIGN_LEN + 4] = {'\0'};
memmove(fnName, "Fn ", 3);
memmove(fnName + 3, name.start, name.length);
var.index = getIndexFromSymbolTable(
&cu->curParser->curModule->moduleVarName, fnName, strlen(fnName));
//若不是函数名,那可能是该模块变量定义在引用处的后面,
//先将行号做为该变量值去声明
if (var.index == -1) {
var.index = declareModuleVar(cu->curParser->vm, cu->curParser->curModule,
name.start, name.length, NUM_TO_VALUE(cu->curParser->curToken.lineNo));
}
}
emitLoadOrStoreVariable(cu, canAssign, var);
}
}
处理顺序:函数调用->局部变量和upvalue->实例域->静态域->类getter方法调用->模块变量
2.1 实例域标识符
class Family < People {
var father
var mother
var child
static var money = "100"
new(f, m, c) {
father = f
mother = m
child = c
money = "1000"
super("wbf", "male")
}
}
编译类中的实例域标识符,如上述例子的new方法中的father、mother、child。
- getEnclosingClassBK获得classBK,即new方法的cu的->enclosingUnit->enclosingClassBK。classBK不等于NULL说明当前传入id的cu要么是模块cu正在编译类中语句,要么是类中方法cu正在编译类中方法语句。这里的例子显然是后者。
- getIndexFromSymbolTable从classBK->fields找实例域,与编译类定义实例域博客呼应
- 如果发现等号,则调用expression生成指令将等号右边值计算出来放置栈顶
- 在实例的fields加载(将实例域push于栈顶)或者存储(赋值实例的fields)
这里再总结一下编译father 等实例变量的函数顺序。结合了之二和之三博客列举的函数:
compileClassDefinition -> compileClassBody ->compileMethod ->compileBody ->compileBlock->compileProgram->compileStatment->expression-> id.nud方法(curToken为"father",id.nud中会将"father"识别为实例域,于是调用上述四步)-> matchToken(cu->curParser, TOKEN_ASSIGN) -> expression计算"f"即将f加载入栈 -> STORE_THIS_FIELD->PEEK栈顶的f复制到field。
注意:这里的f是形参,编译new方法的时候完成了局部变量f的声明,而调用new方法的时候完成了局部变量f的赋值。具体来说是,上篇博客的编译方法函数里调用了idMethodSignature处理生成方法签名并调用processParaList处理形参(即局部变量),processParaList里调用declareVariable里面的declareLocalVar定义为了该方法CU里的局部变量,处理形参本质是声明为局部变量;而为该方法"new"的局部变量"f"赋值显然发生在该方法new的调用,可以见本博客2.3节,id.nud如何处理方法调用标识符的
2.1.1 getIndexFromSymbolTable
//table中查找符号symbol 找到后返回索引,否则返回-1
int getIndexFromSymbolTable(SymbolTable* table, const char* symbol, uint32_t length) {
ASSERT(length != 0, "length of symbol is 0!");
uint32_t index = 0;
while (index < table->count) {
if (length == table->datas[index].length &&
memcmp(table->datas[index].str, symbol, length) == 0) {
return index;
}
index++;
}
return -1;
}
2.1.2 emitLoadThis
//生成把实例对象this加载到栈的指令
static void emitLoadThis(CompileUnit* cu) {
Variable var = getVarFromLocalOrUpvalue(cu, "this", 4);
ASSERT(var.scopeType != VAR_SCOPE_INVALID, "get variable failed!");
emitLoadVariable(cu, var);
}
2.1.3 LOAD_THIS_FIELD、STORE_THIS_FIELD
CASE(LOAD_THIS_FIELD): {
//指令流: 1字节的field索引
uint8_t fieldIdx = READ_BYTE();
//stackStart[0]是实例对象this
ASSERT(VALUE_IS_OBJINSTANCE(stackStart[0]), "method receiver should be objInstance.");
ObjInstance* objInstance = VALUE_TO_OBJINSTANCE(stackStart[0]);
ASSERT(fieldIdx < objInstance->objHeader.class->fieldNum, "out of bounds field!");
PUSH(objInstance->fields[fieldIdx]);
LOOP();
}
CASE(STORE_THIS_FIELD): {
//栈顶: field值
//指令流: 1字节的field索引
uint8_t fieldIdx = READ_BYTE();
ASSERT(VALUE_IS_OBJINSTANCE(stackStart[0]), "receiver should be instance!");
ObjInstance* objInstance = VALUE_TO_OBJINSTANCE(stackStart[0]);
ASSERT(fieldIdx < objInstance->objHeader.class->fieldNum, "out of bounds field!");
objInstance->fields[fieldIdx] = PEEK();
LOOP();
}
CASE(LOAD_FIELD): {
//栈顶:实例对象
//指令流: 1字节的field索引
uint8_t fieldIdx = READ_BYTE(); //获取待加载的字段索引
Value receiver = POP(); //获取消息接收者
ASSERT(VALUE_IS_OBJINSTANCE(receiver), "receiver should be instance!");
ObjInstance* objInstance = VALUE_TO_OBJINSTANCE(receiver);
ASSERT(fieldIdx < objInstance->objHeader.class->fieldNum, "out of bounds field!");
PUSH(objInstance->fields[fieldIdx]);
LOOP();
}
CASE(STORE_FIELD): {
//栈顶:实例对象 次栈顶:filed值
//指令流: 1字节的field索引
uint8_t fieldIdx = READ_BYTE(); //获取待加载的字段索引
Value receiver = POP(); //获取消息接收者
ASSERT(VALUE_IS_OBJINSTANCE(receiver), "receiver should be instance!");
ObjInstance* objInstance = VALUE_TO_OBJINSTANCE(receiver);
ASSERT(fieldIdx < objInstance->objHeader.class->fieldNum, "out of bounds field!");
objInstance->fields[fieldIdx] = PEEK();
LOOP();
}
由于实例域的实例变量是存在于classBK->filelds的,因此如果在编译类方法,那么this已经在栈中,可以直接加载filelds。如果在类中而不在类方法中,那么就需要先加载this,再加载filelds
2.2 静态域标识符
class Family < People {
var father
var mother
var child
static var money = "100"
new(f, m, c) {
father = f
mother = m
child = c
money = "1000"
// child = money
super("wbf", "male")
}
}
编译类中静态域标识符,比如上述例子的money,步骤如下:
- classBK不等于NULL说明当前传入id的cu要么是模块cu正在编译类中语句,要么是类中方法cu正在编译类中方法语句。这里的例子显然是后者
- 静态变量"cls 静态域属性名 name"存储在模块变量的LocalVar中,实际值存储在运行时栈因为充当模块局部变量,被所有对象共享。调用getVarFromLocalOrUpvalue在模块cu-LocalVar里寻找索引时,上述例子必然找不到。故调用findUpvalue想外层CU继续寻找cu->LocalVar,显然外层CU是模块CU,所以findUpvalue的第一次findLocal就会找到同时加入方法CU->upvalue
- 调用emitLoadOrStoreVariable,由于传入的var的type是局部变量,则存储或者加载都是操作stackStart。但是本例子类型是upvalue,所以操作的是curFrame->closure->upvalues[READ_BYTE()]
这里再总结一下编译money的函数顺序。结合了之二和之三博客列举的函数:
compileClassDefinition -> compileClassBody ->compileMethod ->compileBody ->compileBlock->compileProgram->compileStatment->expression-> id.nud方法(由于后面是等于,所以canAssign为True)->emitLoadOrStoreVariable->matchToken(cu->curParser, TOKEN_ASSIGN)->expression计算"1000"即将1000加载入栈->结束。
注意:注释的语句“child = money” 其实同理了,child 执行了expression的id.nud里的实例域部分,money执行了expression的id.nud里的静态域部分
所以多篇博客重点是搞清楚id.nud到底是什么情况下执行的,因为这部分书里没有全局的解释,只有局部的每个函数的解释。
//从局部变量和upvalue中查找符号name
static Variable getVarFromLocalOrUpvalue(CompileUnit* cu, const char* name, uint32_t length) {
Variable var;
//默认为无效作用域类型,查找到后会被更正
var.scopeType = VAR_SCOPE_INVALID;
var.index = findLocal(cu, name, length);
if (var.index != -1) {
var.scopeType = VAR_SCOPE_LOCAL;
return var;
}
var.index = findUpvalue(cu, name, length);
if (var.index != -1) {
var.scopeType = VAR_SCOPE_UPVALUE;
}
return var;
}
//查找name指代的upvalue后添加到cu->upvalues,返回其索引,否则返回-1
static int findUpvalue(CompileUnit* cu, const char* name, uint32_t length) {
if (cu->enclosingUnit == NULL) { //如果已经到了最外层仍未找到,返回-1.
return -1;
}
//进入了方法的cu并且查找的不是静态域,即不是方法的Upvalue,那就没必要再往上找了
if (!strchr(name,' ') && cu->enclosingUnit->enclosingClassBK != NULL) {
return -1;
}
//查看name是否为直接外层的局部变量
int directOuterLocalIndex = findLocal(cu->enclosingUnit, name, length);
//若是,将该外层局部变量置为upvalue,
if (directOuterLocalIndex != -1) {
cu->enclosingUnit->localVars[directOuterLocalIndex].isUpvalue = true;
return addUpvalue(cu, true, (uint32_t)directOuterLocalIndex);
}
//向外层递归查找
int directOuterUpvalueIndex = findUpvalue(cu->enclosingUnit, name, length);
if (directOuterUpvalueIndex != -1) {
return addUpvalue(cu, false, (uint32_t)directOuterUpvalueIndex);
}
//执行到此说明没有该upvalue对应的局部变量,返回-1
return -1;
}
//添加upvalue到cu->upvalues,返回其索引.若已存在则只返回索引
static int addUpvalue(CompileUnit* cu, bool isEnclosingLocalVar, uint32_t index) {
uint32_t idx = 0;
while (idx < cu->fn->upvalueNum) {
//如果该upvalue已经添加过了就返回其索引
if (cu->upvalues[idx].index == index &&
cu->upvalues[idx].isEnclosingLocalVar == isEnclosingLocalVar) {
return idx;
}
idx++;
}
//若没找到则将其添加
cu->upvalues[cu->fn->upvalueNum].isEnclosingLocalVar = isEnclosingLocalVar;
cu->upvalues[cu->fn->upvalueNum].index = index;
return cu->fn->upvalueNum++;
}
//生成加载或存储变量的指令
static void emitLoadOrStoreVariable(CompileUnit* cu, bool canAssign, Variable var) {
if (canAssign && matchToken(cu->curParser, TOKEN_ASSIGN)) {
expression(cu, BP_LOWEST); //计算'='右边表达式的值
emitStoreVariable(cu, var); //为var生成赋值指令
} else {
emitLoadVariable(cu, var); //为var生成读取指令
}
}
2.3 方法调用标识符、局部变量标识符
class A {
new() {
var a = 10
pr(a)
}
pr(num) {
System.print(num)
)
}
类名即模块变量必须大写,否则编译器会把标识符按照方法来处理,方法名是小写
以上述例子来举例,编译new方法的时候调用了同类方法"pr(a)",在编译类实例方法pr时,将pr的形参声明在了pr的方法CU的localVar中,调用pr(a)会时调用id来识别pr这个标识符完成方法调用功能,当然也会完成实参赋值形参,具体执行顺序如下:
compileClassDefinition -> compileClassBody -> compileMethod -> compileBody -> compileBlock -> compileProgram -> compileStatment -> expression -> id.nud(识别pr为同类方法调用,于是入栈this然后调用emitMethodCall -> processArgList -> expression -> id.nud处理实参a(curtoken为a,a被id.nud识别为局部变量,也为方法pr的实参) -> id函数里的getVarFromLocalOrUpvalue获取在2.1节的processParaList声明的局部变量索引 -> emitLoadOrStoreVariable根据索引生成存储或加载指令,本例子是生成加载局部变量指令,也就是将局部变量a push到new方法cu的栈顶 -> emitCallBySignature根据方法签名,在vm->allMethodNames里找到对应方法索引(由上篇博客编译方法提供),生成调用方法指令OPCODE_CALL0+sign->argNum,OPCODE_CALL0指令下虚拟机会根据索引从this中找到实例的类然后找到类中的方法,即class->methods.datas[index],这里面存储着方法闭包,在编译定义方法的时候赋值进去的,上篇博客分析过。
注意,加载实参a这一步,实际是把new方法CU的运行时栈中局部变量a的值加载到了new方法CU的运行时栈栈顶,new方法中调用了pr方法,因此new方法cu的运行时栈栈顶等于pr方法cu的运行时栈栈底,所以等价于完成了pr方法形参(已经声明在pr方法cu->localVar中的局部变量)的赋值。
总结:所以实参赋值已经声明的形参局部变量是在id函数里的局部变量处理部分,
完成方法调用功能是emitMethodCall函数的emitCallBySignature,入栈对象,根据方法名获取索引,在对象所属类根据索引获得编译好的方法闭包然后执行。注意传入的sign.name未任何处理,因为不同类的同名方法在全局符号表只存储一份,具有一样的索引,无需使用类名再次区分。
2.3.1 emitMethodCall processArgList emitCallBySignature
//生成方法调用指令,包括getter和setter
static void emitMethodCall(CompileUnit* cu, const char* name,
uint32_t length, OpCode opCode, bool canAssign) {
Signature sign;
sign.type = SIGN_GETTER;
sign.name = name;
sign.length = length;
//若是setter则生成调用setter的指令
if (matchToken(cu->curParser, TOKEN_ASSIGN) && canAssign) {
sign.type = SIGN_SETTER;
sign.argNum = 1; //setter只接受一个参数
//载入实参(即'='右边所赋的值),为下面方法调用传参
expression(cu, BP_LOWEST);
emitCallBySignature(cu, &sign, opCode);
} else {
emitGetterMethodCall(cu, &sign, opCode);
}
}
//生成getter或一般method调用指令
static void emitGetterMethodCall(CompileUnit* cu, Signature* sign, OpCode opCode) {
Signature newSign;
newSign.type = SIGN_GETTER; //默认为getter,假设下面的两个if不执行
newSign.name = sign->name;
newSign.length = sign->length;
newSign.argNum = 0;
//如果是method,有可能有参数列表 在生成调用方法的指令前必须把参数入栈 否则运行方法时除了会获取到错误的参数(即栈中已有数据)外,g还会在从方法返回时,错误地回收参数空间而导致栈失衡
//下面调用的processArgList是把实参入栈,供方法使用
if (matchToken(cu->curParser, TOKEN_LEFT_PAREN)) { //判断后面是否有'('
newSign.type = SIGN_METHOD;
//若后面不是')',说明有参数列表
if (!matchToken(cu->curParser, TOKEN_RIGHT_PAREN)) {
processArgList(cu, &newSign);
consumeCurToken(cu->curParser, TOKEN_RIGHT_PAREN, "expect ')' after argument list!");
}
}
//对method来说可能还传入了块参数
if (matchToken(cu->curParser, TOKEN_LEFT_BRACE)) {
newSign.argNum++;
//进入本if块时,上面的if块未必执行过,
//此时newSign.type也许还是GETTER,下面要将其设置为METHOD
newSign.type = SIGN_METHOD;
CompileUnit fnCU;
initCompileUnit(cu->curParser, &fnCU, cu, false);
Signature tmpFnSign = {SIGN_METHOD, "", 0, 0}; //临时用于编译函数
if (matchToken(cu->curParser, TOKEN_BIT_OR)) { //若块参数也有参数
processParaList(&fnCU, &tmpFnSign); //将形参声明为函数的局部变量
consumeCurToken(cu->curParser, TOKEN_BIT_OR, "expect '|' after argument list!");
}
fnCU.fn->argNum = tmpFnSign.argNum;
//编译函数体,将指令流写进该函数自己的指令单元fnCu
compileBody(&fnCU, false);
#if DEBUG
//以此函数被传给的方法来命名这个函数, 函数名=方法名+" block arg"
char fnName[MAX_SIGN_LEN + 10] = {'\0'}; //"block arg\0"
uint32_t len = sign2String(&newSign, fnName);
memmove(fnName + len, " block arg", 10);
endCompileUnit(&fnCU, fnName, len + 10);
#else
endCompileUnit(&fnCU);
#endif
}
//如果是在子类构造函数中
if (sign->type == SIGN_CONSTRUCT) {
if (newSign.type != SIGN_METHOD) {
COMPILE_ERROR(cu->curParser, "the form of supercall is super() or super(arguments)");
}
newSign.type = SIGN_CONSTRUCT;
}
//根据签名生成调用指令.如果上面的三个if都未执行,此处就是getter调用
emitCallBySignature(cu, &newSign, opCode);
}
//为实参列表中的各个实参生成加载实参的指令
static void processArgList(CompileUnit* cu, Signature* sign) {
//由主调方保证参数不空
ASSERT(cu->curParser->curToken.type != TOKEN_RIGHT_PAREN &&
cu->curParser->curToken.type != TOKEN_RIGHT_BRACKET, "empty argument list!");
do {
if (++sign->argNum > MAX_ARG_NUM) {
COMPILE_ERROR(cu->curParser, "the max number of argument is %d!", MAX_ARG_NUM);
}
expression(cu, BP_LOWEST); //加载实参
} while (matchToken(cu->curParser, TOKEN_COMMA));
}
//通过签名编译方法调用,包括callX和superX指令
static void emitCallBySignature(CompileUnit* cu, Signature* sign, OpCode opcode) {
char signBuffer[MAX_SIGN_LEN];
uint32_t length = sign2String(sign, signBuffer);
//确保签名录入到vm->allMethodNames中
int symbolIndex = ensureSymbolExist(cu->curParser->vm,
&cu->curParser->vm->allMethodNames, signBuffer, length);
writeOpCodeShortOperand(cu, opcode + sign->argNum, symbolIndex);
//此时在常量表中预创建一个空slot占位,将来绑定方法时再装入基类
if (opcode == OPCODE_SUPER0) {
writeShortOperand(cu, addConstant(cu, VT_TO_VALUE(VT_NULL)));
}
}
2.4 模块变量标识符
由于上篇博客只是在编译类内的定义,并不涉及模块变量(类名就是模块变量),模块变量有declareModuleVar和defineModuleVar,objModule->moduleVarName、objModule->moduleVarValue为存储的结构
这里调用getIndexFromSymbolTable在curModule->moduleVarName如果没有找到,有可能是先使用再定义,所以先调用declareModuleVar先定义,并以行号数字作为值,defineModuleVar定义模块变量的时候,如果检测到了数字,则会再次赋正确的值。
最后调用emitLoadOrStoreVariable生成存储或者加载模块变量的指令。
//声明模块变量,与defineModuleVar的区别是不做重定义检查,默认为声明
static int declareModuleVar(VM* vm, ObjModule* objModule,
const char* name, uint32_t length, Value value) {
ValueBufferAdd(vm, &objModule->moduleVarValue, value);
return addSymbol(vm, &objModule->moduleVarName, name, length);
}
//生成加载或存储变量的指令
static void emitLoadOrStoreVariable(CompileUnit* cu, bool canAssign, Variable var) {
if (canAssign && matchToken(cu->curParser, TOKEN_ASSIGN)) {
expression(cu, BP_LOWEST); //计算'='右边表达式的值
emitStoreVariable(cu, var); //为var生成赋值指令
} else {
emitLoadVariable(cu, var); //为var生成读取指令
}
}
2.5 标识符函数调用
- 调用getIndexFromSymbolTable(&cu->curParser->curModule->moduleVarName, id, strlen(id))查找Fn为前缀的模块变量名。
- emitLoadVariable加载函数闭包于栈顶
- 调用processArgList处理实参
- 调用emitCallBySignature(cu, &sign, OPCODE_CALL0);生成调用函数指令,sign.name是call
可以看到,函数的调用是函数闭包.call,这和方法调用this.方法名有区别。编译函数的博客暂时还未整理。