employee.sp
class Employee {
var name
var gender
var age
var salary
static var employeeNum = 0
new(n, g, a, s) {
name = n
gender = g
age = a
salary = s
employeeNum = employeeNum + 1
}
sayHi() {
System.print("My name is " +
name + ", I am a " + gender +
", " + age.toString + "years old")
}
salary {
return salary
}
static employeeNum {
return employeeNum
}
}
manager.sp
本博客分析梳理本书提供的唯一例子的编译函数执行流,复习之前的博客,并补充之前博客未涉及的函数。不涉及第八章内建类的基础核心方法和第九章垃圾回收内容。
import employee for Employee
var xh = Employee.new("xiaohong", "female", 20, 6000)
System.print(xh.salary)
var xm = Employee.new("xiaoming", "male", 23, 8000)
System.print(xm.salary)
System.print(Employee.employeeNum)
class Manager < Employee {
var bonus
bonus=(v) {
bonus = v
}
new(n, g, a, s, b) {
super(n, g, a, s)
bonus = b
}
salary {
return super.salary + bonus
}
}
fun employeeInfo() {
System.print("number of employee:" + Employee.employeeNum.toString)
var employeeTitle = Map.new()
employeeTitle["xh"] = "rd"
employeeTitle["xm"] = "op"
employeeTitle["lw"] = "manager"
employeeTitle["lz"] = "pm"
for k (employeeTitle.keys) {
System.print(k + " -> " + employeeTitle[k])
}
var employeeHeight = {
"xh": 170,
"xm": 172,
"lw": 168,
"lz": 173
}
var totalHeight = 0
for v (employeeHeight.values) {
totalHeight = totalHeight + v
}
System.print("averageHeight: %(totalHeight / employeeHeight.count)")
var allEmployee = ["xh", "xm", "lw", "lz"]
for e (allEmployee) {
System.print(e)
}
allEmployee.add("xl")
System.print("all employee are:%(allEmployee.toString)")
var idx = 0
var count = allEmployee.count
while (idx < count) {
System.print(allEmployee[idx])
idx = idx + 1
}
//System.gc() //可以手动回收内存
var a = 3 + 5 > 9 - 3 ? "yes" : "no"
if (a.endsWith("s")) {
System.print(System.clock)
} else {
System.print("error!!!!!")
}
var str = "hello, world."
System.print(str[-1..0])
}
var lw = Manager.new("laowang", "male", 35, 13000, 2000)
System.print(lw.salary)
lw.bonus=3100
System.print(lw.salary)
var lz = Manager.new("laozheng", "male", 36, 15000, 2300)
System.print(lz.salary)
var thread = Thread.new(employeeInfo)
thread.call()
代码类别总结
- import,实质上是调用了系统类System的方法来编译新的模块
- var关键字,用于变量的定义,变量则包括局部变量、模块变量、实例变量、静态变量(类变量)
- 类方法调用,类名.方法名
- class用于类的定义,类中包含实例变量、类变量、一般方法、类方法、构造方法、getter方法、setter方法
- fun关键字用于函数的定义
- 中括号的List字面量创建方法,本质是调用了List类的构造方法。大括号的Map字面量创建方法,本质是调用了Map类的构造方法
- ifelse、for、while作用域内都可以继续定义更深的局部变量,作用域结束局部变量会自动释放
- 代码是由关键字和表达式构成的,而表达式包括操作数和操作符op,比如a=b,类名.方法名。操作数(名字)都会有nud方法,操作符op都会有led方法,通过expression来执行操作数和操作符的方法得到表达式的值。这也叫做语法分析。而关键字一般会由编译器识别,从而执行特定的编译处理逻辑。
1. import employee for Employee
模块变量被导入后是模块变量还是局部变量,取决于关键字import的作用域。这里Employee被声明定义成了模块变量,存在cu->curParser->curModule->moduleVarName和cu->curParser->curModule->moduleVarValue中,而模块值为Class类型转Value的Employee类。
1.1 compileImport
//编译import导入
static void compileImport(CompileUnit* cu) {
// import "foo"
//将按照以下形式处理:
// System.importModule("foo")
// import foo for bar1, bar2
//将按照以下形式处理:
// var bar1 = System.getModuleVariable("foo", "bar1")
// var bar2 = System.getModuleVariable("foo", "bar2")
consumeCurToken(cu->curParser, TOKEN_ID, "expect module name after export!");
//备份模块名token
Token moduleNameToken = cu->curParser->preToken;
//导入时模块的扩展名不需要,有可能用户会把模块的扩展名加上,
//比如import sparrow.sp, 这时候就要跳过扩展名
if (cu->curParser->preToken.start[cu->curParser->preToken.length] == '.') {
printf("\nwarning!!! the imported module needn`t extension!, compiler try to ignor it!\n");
//跳过扩展名
getNextToken(cu->curParser); //跳过'.'
getNextToken(cu->curParser); //跳过"sp"
}
//把模块名转为字符串,存储为常量
ObjString* moduleName = newObjString(cu->curParser->vm,
moduleNameToken.start, moduleNameToken.length);
uint32_t constModIdx = addConstant(cu, OBJ_TO_VALUE(moduleName));
//1 为调用system.importModule("foo")压入参数system
emitLoadModuleVar(cu, "System");
//2 为调用system.importModule("foo")压入参数foo
writeOpCodeShortOperand(cu, OPCODE_LOAD_CONSTANT, constModIdx);
//3 现在可以调用system.importModule("foo")
emitCall(cu, 1, "importModule(_)", 15);
//回收返回值args[0]所在空间
writeOpCode(cu, OPCODE_POP);
//如果后面没有关键字for就导入结束
if (!matchToken(cu->curParser, TOKEN_FOR)) {
return;
}
//循环编译导入的模块变量(以逗号分隔),
//如:import foo for bar1, bar2中的bar1, bar2
do {
consumeCurToken(cu->curParser, TOKEN_ID,
"expect variable name after 'for' in import!");
//在本模块中声明导入的模块变量
uint32_t varIdx = declareVariable(cu,
cu->curParser->preToken.start, cu->curParser->preToken.length);
//把模块变量转为字符串,存储为常量
ObjString* constVarName = newObjString(cu->curParser->vm,
cu->curParser->preToken.start, cu->curParser->preToken.length);
uint32_t constVarIdx = addConstant(cu, OBJ_TO_VALUE(constVarName));
// 1 为调用System.getModuleVariable("foo", "bar1") 压入system
emitLoadModuleVar(cu, "System");
// 2 为调用System.getModuleVariable("foo", "bar1") 压入foo
writeOpCodeShortOperand(cu, OPCODE_LOAD_CONSTANT, constModIdx);
// 3 为调用System.getModuleVariable("foo", "bar1") 压入bar1
writeOpCodeShortOperand(cu, OPCODE_LOAD_CONSTANT, constVarIdx);
// 4 调用System.getModuleVariable("foo", "bar1")
emitCall(cu, 2, "getModuleVariable(_,_)", 22);
//此时栈顶是system.getModuleVariable("foo", "bar1")的返回值,
//即导入的模块变量的值 下面将其同步到相应变量中
defineVariable(cu, varIdx);
} while (matchToken(cu->curParser, TOKEN_COMMA));
}
暂不分析importModule()、getModuleVariable(,_)
(一个函数的字面量如数字、字符串、类名、函数、模块名、模块变量名都需要存储于常量表)
- 调用addConstant将模块名foo存储在常量表cu->fn->constants中
- 将模块变量System(事先已保存于模块变量表fn->module->moduleVarValue)入栈、常量模块名foo入栈、emitcall调用方法importModule(_)。该方法应该完成了对foo.sp文件的编译。
- 如果后面有for,则实现do-while循环来给当前fn->cu声明和加载模块变量,declareVariable会根据当前作用于声明bar1、bar2是局部还是模块变量
- 循环:根据当期作用域声明局部 / 模块变量bar1、bar2、将变量名bar1、2存入常量表、将模块变量System入栈、常量模块名foo入栈、常量名bar1、2入栈,emitCall调用getModuleVariable(,)方法、定义该方法返回的模块变量值bar1、bar2.
2. var xh = Employee.new(“xiaohong”, “female”, 20, 6000)
此行代码完成了三件事
- "var xh"在模块作用域声明了一个模块变量xh,
- 调用了Employee类的构造方法
- 将new的Employee类赋值给了xh
2.1 "var xh"在模块作用域声明了一个模块变量xh
见上篇博客虚拟机与之前博客类定义,其实应该从runfile开始(见虚拟机博客),但是这里简单点写从compileProgram开始。
compileProgram -> compileVarDefinition -> declareVariable -> defineVariable
compileProgram 识别到关键字var,调用compileVarDefinition。compileVarDefinition中判断出作用域不在类中,于是调用declareVariable 和defineVariable定义为一般变量,而由于在模块作用域,所以实际上声明定义了模块变量。cu->curParser->curModule->moduleVarName存储名字,cu->curParser->curModule->moduleVarValue存储值
ps:类定义博客详细分析了compileVarDefinition函数,那个时候的函数执行流如下:
compileProgram -> compileClassDefinition -> compileClassBody -> compileVarDefinition
但这里xh显然不是类变量,所以走的是compileVarDefinition中第三种逻辑一般变量的处理,而类定义博客分析的是变量是实例域或者静态域的情况
2.2 调用Employee类的构造方法
在compileVarDefinition里定义xh变量调用declareVariable 和defineVariable前,会先检查xh后面是否有等号,若有则先expression解析表达式,expression执行完毕会将表达式的值置于栈顶,从而defineVariable可以完成变量的赋值。
而expression解析的表达式为“Employee.new(“xiaohong”, “female”, 20, 6000)”。
由于Employee已经被import为模块变量,因此接下来的调用函数链如下:
expression -> id.nud(按模块变量处理) -> emitLoadOrStoreVariable (load了模块变量于线程运行时栈栈顶) -> "."的led方法 -> emitMethodCall执行new方法
emitMethodCall执行new方法后面的函数调用链与博客编译方法标识符的2.3节一样。
注:编译标识符的2.3节是调用类中其他方法,由于在类中,所以不需要实例+“.”+方法名来调用实例方法,而是直接使用实例方法名即可调用实例方法,因此id.nud识别到为类中其他方法调用时便调用了emitMethodCall解析方法传参以及执行标识符对应的方法。
而这里的例子是在模块作用域而不是类中,因此采用了类名+“.”+“new"来调用new方法,所以”."的led方法中调用了emitMethodCall来解析new方法传入参数和执行new方法。
因此两者只是在不同情况下调用了emitMethodCall,所以函数调用链一致。
调用new方法有一些特殊,new既是实例方法也是静态方法,因为new只能类来调用,但是new方法内部难免要访问实例变量。
在compileMethod中,会创建新方法编译单元CU,在其中编译方法语句生成指令,编译完成后生成闭包,然后调用defineMethod将闭包复制到class的methods中,如果该方法是静态方法,则赋值到class的meta类中。
而在编译构造方法new时,调用了两次defineMethod,一次是赋值实例方法,一次是赋值静态方法。
由于new方法的调用形式是类名.new(xxx, xxx),因此,emitMethodCall会执行Employee的元类的new方法,也就是new的静态方法闭包指令。在博客类定义第2.9节中,new的静态方法只执行了三条指令,分别是OPCODE_CONSTRUCT创建实例、CALLx调用new实例方法、OPCODE_RETURN返回实例对象于栈顶作为expression的返回。
注: OPCODE_CONSTRUCT前必须执行CREATE_CLASS将类置于执行当前脚本创建的新框架的运行时栈栈底,而CREATE_CLASS显然是"import employee for Employee"的时候,完成了对脚本employee.sp中的类Employee的编译,编译类Employee产生的指令保存于模块CU中,所以,因为import的原因,manager.sp模块CU的指令流会包含着employee.sp模块编译类定义Employee的一些指令流比如肯定有的就是CREATE_CLASS,这个指令由类定义函数生成。
executeInstruction执行manager.sp模块CU的指令流时也会执行类定义Employee的指令流。
(import生成的都是调用system方法的指令,因此需要学习第九章的System类,所以具体细节下篇博客分析)
"."的led方法
SymbolBindRule Rules[] = {
...略
/* TOKEN_DOT */ INFIX_SYMBOL(BP_CALL, callEntry),
/* TOKEN_DOT_DOT */ INFIX_OPERATOR("..", BP_RANGE),
...略
}
//'.'.led() 编译方法调用,所有调用的入口
static void callEntry(CompileUnit* cu, bool canAssign) {
//本函数是'.'.led(),curToken是TOKEN_ID
consumeCurToken(cu->curParser,
TOKEN_ID, "expect method name after '.'!");
//生成方法调用指令
emitMethodCall(cu, cu->curParser->preToken.start,
cu->curParser->preToken.length, OPCODE_CALL0, canAssign);
}
3 “xh.salary”
暂时不考虑原生方法System.print。
xh.salary是调用了salary的getter方法访问基类实例域(成员变量),注意:sparrow语言没有public、private、protected。
实例域只能通过实例方法访问比如getter、静态域只能通过静态方法访问。
因此当使用xh.salary的时候,".“的led方法只会认为salary是一个方法,因而调用emitMethodCall去生成salary方法指令,而不会去找实例域fileds中的salary。这个格式显然salary是一个getter方法。而实例方法中出现标识符salary,则会默认是this.salary,因此id.nud会识别salary,从而发现salary是一个实例域变量。
具体函数链如下
compileProgram -> compileStatment -> expression -> id.nud(emitLoadOrStoreVariable生成加载模块变量指令)-> “.”.led -> emitMethodCall(生成调用getter方法salary指令)-> emitGetterMethodCall -> emitCallBySignature(生成调用getter方法指令) -> (虚拟机部分)-> STORE_CUR_FRAME -> createFrame(vm, curThread, (ObjClosure)method->obj, argNum)(为getter方法创建新框架) -> LOAD_CUR_FRAME(执行salary方法闭包的指令流)*
getter方法salary中只有一句"return salary”。
编译这句代码的函数调用链如下:
compileMethod -> compileBody -> compileBlock -> compileProgram -> compileStatment -> compileReturn -> expression(计算表达式"salary") -> id.nud(识别到salary为实例域) -> getEnclosingClassBK(找到模块CU的ClassBookKeep classBK )-> getIndexFromSymbolTable(传入classBK->fields找到实例域salary) -> OPCODE_LOAD_THIS_FIELD(将实例域salary)
注意:模块CU的ClassBookKeep的fileds保存着Employee类实例域的索引
objInstance->fileds保存着实例域的值。
由此可见,虽然执行xh.salary的时候,保存Employee类的实例域salary的索引的ClassBookKeep 已经不存在了(因为CU->ClassBookKeep 只存在于编译时,模块CU只存在于编译时,执行endCompileUnit时会把cu->upvalue放入新创建的闭包,赋值class->methods也只会将闭包赋值,endCompileUnit返回也只会返回指令流fn,因此编译完成并执行endCompileUnit后,编译单元CU就不再使用了,executeInstruction部分只会涉及到执行指令流ObjClosure->fn)。但是在编译Employee类的getter方法salary中的"return salary"时,处于编译时期,模块CU的ClassBookKeep保存着Employee类的fileds(存储着Employee类实例域的索引),因此id.nud遇到salary时,仍然可以通过ClassBookKeep找到该实例域的索引并提前写入指令的操作数中。
3.1 compileReturn
//编译return
inline static void compileReturn(CompileUnit* cu) {
if (PEEK_TOKEN(cu->curParser) == TOKEN_RIGHT_BRACE) { //空返回值
//空return,NULL做为返回值
writeOpCode(cu, OPCODE_PUSH_NULL);
} else { //有返回值
expression(cu, BP_LOWEST);
}
writeOpCode(cu, OPCODE_RETURN); //将上面栈顶的值返回
}
4 编译类定义 Manager
class Manager < Employee {
var bonus
bonus=(v) {
bonus = v
}
new(n, g, a, s, b) {
super(n, g, a, s)
bonus = b
}
salary {
return super.salary + bonus
}
}
compileModule -> (循环执行)compileProgram -> compileClassDefinition
具体参考博客类定义
4.1 “class Manager < Employee”
编译完这句话后,Manager 已经被当做模块变量置于cu->curParser->curModule->moduleVarName和cu->curParser->curModule->moduleVarValue中,而模块值为Class类型转Value的Manager类。同时基类Employee在CREATE_CLASS也被保存于class->superClass中。类定义博客第二节有全面解析。
接下来进入大括号类体,compileClassBody。
4.2 “var bonus”
compileClassBody -> compileVarDefinition
实例域变量声明,声明于CU->ClassBookKeep->fileds。
4.3 bonus的setter方法
compileClassBody -> compileMethod -> idMethodSignature -> trySetter(解析小括号中的形参,调用declareVariable声明为局部变量)-> sign2String -> declareMethod(方法名并声明于vm->allMethodNames) -> compileBody -> compileBlock -> compileProgram -> compileStatment -> expression (curToken为bonus) -> id.nud(bonus被识别标识符bonus为实例域)-> getIndexFromSymbolTable(获得该实例域索引)-> matchToken(匹配到等号)-> expression(curToken为v) -> id.nud(v被识别为局部变量)-> getVarFromLocalOrUpvalue(在cu->localVar中获得该局部变量索引)-> emitLoadOrStoreVariable -> emitLoadVariable(根据索引加载该局部变量于线程objThread运行时栈栈顶esp,作为expression的值)-> OPCODE_STORE_FIELD(生成存储实例域指令,完成实例域赋值)
-> endCompileUnit -> defineMethod
注: trySetter中调用declareVariable时,由于compileClassBody前已经enterScope,所以declareVariable不会声明为模块变量而是局部变量。
另外,上述分析中有两个expression,执行第二个expression时,curToken为v,所以getNextToken时得到的是EOF,因此执行完id.nud就退出了,不会执行while循环,因为BP_NONE小于BP_LOWEST。
在生成OPCODE_STORE_FIELD后,第一个expression的id.nud也执行完毕了,此时的curToken也是EOF,同理,expression也直接退出。
4.5 编译Manager类的new方法
compileClassBody -> compileMethod -> idMethodSignature(sign->type设置为SIGN_CONSTRUCT) -> processParaList(声明形参为new方法CU的localVar)-> sign2String(构造new方法的sign)-> declareMethod(声明new方法于vm->allMethodNames,形式为"new( _ , _ , _ , _ ,_)") -> compileBody -> endCompileUnit -> defineMethod -> emitCreateInstance -> defineMethod
详细可见博客类定义2.8、2.9节
4.5.1 编译 “super(n, g, a, s)”
首先需要再次强调的是,对于方法的调用,如果是实例方法,则调用形式是实例名.实例方法,实例会先压入栈,实例方法编译时会保存在实例对应的类class-methods[idx]中,索引idx保存在VM->allMethodNames中;如果是静态方法,调用形式是类名.静态方法,类会先压入栈,静态方法会保存在类的元类metaClass->methods[idx],同样索引idx保存在VM->allMethodNames。
所以这里在Manager类中对super方法的调用实际是调用Employee类的元类的new方法。而在编译Employee类的new方法时,new方法已经被保存于Employee类的元类metaClass->methods中
compileBody -> compileBlock -> compileProgram -> compileStatment -> expression(当前curToken为super) -> super.nud(标识符super被识别为super,于是执行super.nud)-> getEnclosingClassBK(获取当前 方法的类)-> emitLoadThis -> emitGetterMethodCall(传入的参数是enclosingClassBK->signature,signature为new( _ , _ , _ , _ ,),以及OPCODE_SUPER0,说明调用的是基类中与子类的同名方法,名字为new( _ , _ , _ , _ ,),也就是调用了基类构造方法)-> processArgList(加载实参) -> emitCallBySignature (生成super0的调用指令并且在常量表与addConstant创建了基类名空位,并生成写入该空位索引为操作数指令)
值得注意的是,在new的OPCODE_CONSTRUCT前一定执行了CREATE_CLASS,这里CREATE_CLASS会把Manager类已经被放入了startStack[0],执行完OPCODE_CONSTRUCT后,Manager类的实例会覆盖startStack[0],然后执行CALLX,即new实例方法的代码,在这里第一句就是SUPER0,super0不像call0中包含getClassOfObj会找到类的元类,执行super0的时候,栈顶已经是实例了(这里是Manager类的实例),所以直接执行new的实例方法(此例子中Employee类的new的实例方法完成了实例中多个实例域的赋值)。
注:有一个稍微难理解的点,编译调用super方法调用emitCallBySignature时,由于最终会在基类的元类metaCalss->methods寻找new方法,所以需要先压栈基类,因此生成了常量表空位索引指向基类名。当Manager类的new方法编译完成后,调用defineMethod将方法闭包赋值到metaClass->methods时,会执行patchOperand修正操作数,当patchOperand识别到SUPER0指令的时候,会将常量表空位填充基类。这样当虚拟机真正执行new方法的super方法调用时,SUPER0才能从常量表找到基类值,从而在基类的元类找到new方法进行执行。
4.5.1.1 super.nud、SUPER0等相关代码
//"super".nud()
static void super(CompileUnit* cu, bool canAssign) {
ClassBookKeep* enclosingClassBK = getEnclosingClassBK(cu);
if (enclosingClassBK == NULL) {
COMPILE_ERROR(cu->curParser, "can`t invoke super outside a class method!");
}
//此处加载this,是保证参数args[0]始终是this对象,尽管对基类调用无用
emitLoadThis(cu);
//判断形式super.methodname()
if (matchToken(cu->curParser, TOKEN_DOT)) {
consumeCurToken(cu->curParser, TOKEN_ID, "expect name after '.'!");
emitMethodCall(cu, cu->curParser->preToken.start,
cu->curParser->preToken.length, OPCODE_SUPER0, canAssign);
} else {
//super():调用基类中与关键字super所在子类方法同名的方法
emitGetterMethodCall(cu, enclosingClassBK->signature, OPCODE_SUPER0);
}
}
//生成把实例对象this加载到栈的指令
static void emitLoadThis(CompileUnit* cu) {
Variable var = getVarFromLocalOrUpvalue(cu, "this", 4);
ASSERT(var.scopeType != VAR_SCOPE_INVALID, "get variable failed!");
emitLoadVariable(cu, var);
}
//-------------- 虚拟机super0节选代码段 -------------
......略
CASE(SUPER0):
//指令流1: 2字节的method索引
//指令流2: 2字节的基类常量索引
//因为还有个隐式的receiver(就是下面的args[0]), 所以参数个数+1.
argNum = opCode - OPCODE_SUPER0 + 1;
index = READ_SHORT();
args = curThread->esp - argNum;
//在函数bindMethodAndPatch中实现的基类的绑定
class = VALUE_TO_CLASS(fn->constants.datas[READ_SHORT()]);
invokeMethod:
......略
#define VALUE_TO_OBJ(value) (value.objHeader)
#define VALUE_TO_CLASS(value) ((Class*)VALUE_TO_OBJ(value))
4.5.2 第二句代码 “bonus = b”
上小节super调用后,基类Employee已经位于栈顶,因此这个栈顶的Employee实例就相当于this,也相当于实例.实例域的实例,id.nud识别bonus为实例域后,会将b的值store给Employee实例中的实例域"bonus"
具体执行流如下同4.3节bonus的setter方法。
值得注意的是,这里编译的是方法内部,在compileMethod中,既可以访问实例域,也可以调用方法,所以bonus其实既可以理解成store实例域,也可以理解成setter方法调用,b为setter传参。两者最后效果一样。但是id.nud的顺序是先识别为实例域,最后识别为同类方法,所以这里还是当做实例域处理。
而如果是在compileClassBody中,那么类体只有两种东西:变量、方法,所以bonus会被识别为方法。
5. getter方法salary
compileClassBody -> compileMethod -> idMethodSignature(识别为getter,无需处理形参) -> sign2String(生成getter的sign)-> declareMethod -> compileBody -> endCompileUnit -> defineMethod
其中compileBody -> compileBlock -> compileProgram -> compileStatment -> compileReturn -> expression -> super.nud -> emitMethodCall(传入参数是"salary") -> emitGetterMethodCall(preToken为salary)-> emitCallBySignature -> sign2String(生成salary的getter方法的sign) -> ensureSymbolExist(拿到salary的getter方法的sign在vm->allMethodnames的索引后,作为OPCODE_SUPER0的操作数) -> OPCODE_SUPER0(生成OPCODE_SUPER0)-> addConstant(并生成写入该空位索引为操作数指令)-> “+”.led(生成add指令) -> id.nud(curToken为bonus)->加载bonus实例变量与栈顶作为 add指令操作数 -> OPCODE_RETURN
和之间super(___ , ___, )一样,先加载patchOperand修正后的基类,根据getter方法salary的索引,在基类的methods中调用同名方法。
当然,expression执行完还会执行后面的加号"+"的led方法,最后return表达式的值
//编译return
inline static void compileReturn(CompileUnit* cu) {
if (PEEK_TOKEN(cu->curParser) == TOKEN_RIGHT_BRACE) { //空返回值
//空return,NULL做为返回值
writeOpCode(cu, OPCODE_PUSH_NULL);
} else { //有返回值
expression(cu, BP_LOWEST);
}
writeOpCode(cu, OPCODE_RETURN); //将上面栈顶的值返回
}
6 编译函数 employeeInfo
compileProgram -> compileFunctionDefinition -> declareVariable -> initCompileUnit ->
processParaList -> compileBody -> endCompileUnit -> defineVariable
6.1 编译函数定义 compileFunctionDefinition
//编译函数定义
static void compileFunctionDefinition(CompileUnit* cu) {
// 本语言完全面向对象,
// (一)
// 函数定义的形式是:
// var function = Fn.new {|形参|
// 函数体代码
// }
// Fn.new返回的是{}内函数的闭包
// 函数调用的形式是"函数闭包.call(...)"
// 但是传统上
// (二)
// 函数定义的形式是:
// function 函数名(形参) {
// 函数体代码
// }
// 函数调用形式是:
// 函数名(实参)
//fun关键字只用在模块作用域中
if (cu->enclosingUnit != NULL) {
COMPILE_ERROR(cu->curParser, "'fun' should be in module scope!");
}
consumeCurToken(cu->curParser, TOKEN_ID, "missing function name!");
//函数名加上"Fn "前缀做为模块变量存储
char fnName[MAX_SIGN_LEN + 4] = {'\0'}; //"fn xxx\0"
memmove(fnName, "Fn ", 3); //把函数名加上"Fn "前缀后做为变量名
memmove(fnName + 3, cu->curParser->preToken.start,
cu->curParser->preToken.length);
uint32_t fnNameIndex = declareVariable(cu, fnName, strlen(fnName));
//生成fnCU,专用于存储函数指令流
CompileUnit fnCU;
initCompileUnit(cu->curParser, &fnCU, cu, false);
Signature tmpFnSign = {SIGN_METHOD, "", 0, 0}; //临时用于编译函数
consumeCurToken(cu->curParser,
TOKEN_LEFT_PAREN, "expect '(' after function name!");
//若有形参则将形参声明局部变量
if (!matchToken(cu->curParser, TOKEN_RIGHT_PAREN)) {
processParaList(&fnCU, &tmpFnSign); //将形参声明为函数的局部变量
consumeCurToken(cu->curParser,
TOKEN_RIGHT_PAREN, "expect ')' after parameter list!");
}
fnCU.fn->argNum = tmpFnSign.argNum;
consumeCurToken(cu->curParser,
TOKEN_LEFT_BRACE, "expect '{' at the beginning of method body.");
//编译函数体,将指令流写进该函数自己的指令单元fnCu
compileBody(&fnCU, false);
#if DEBUG
endCompileUnit(&fnCU, fnName, strlen(fnName));
#else
endCompileUnit(&fnCU);
#endif
//将栈顶的闭包写入变量
defineVariable(cu, fnNameIndex);
}
- 给函数名加上Fn前缀,并定义为模块变量名于cu->curParser->curModule->moduleVarName
- 创建函数编译单元fnCU
- 如果有形参,则调用processParaList处理形参(声明为局部变量),签名结构tmpFnSign临时保存参数个数
- compileBody -> endCompileUnit 编译函数体,栈顶生成函数闭包
- defineVariable赋值函数闭包于fn->module->moduleVarValue
注:cu->curParser->curModule和fn->module指向同一个ObjModule,在博客虚拟机6.4节的compileModule中,initParser和initCompileUnit配合endCompileUnit完成了这件事。
6.2 编译 “var employeeTitle = Map.new()”
和var xh = Employee.new(“xiaohong”, “female”, 20, 6000)类似
- var employeeTitle 声明局部变量employeeTitle
- 创建了一个Map类的实例于栈顶
- 将Map类的实例赋值给了employeeTitle
唯一不同的是第二点
//不关注左操作数的符号称为前缀符号
//用于如字面量,变量名,前缀符号等非运算符
#define PREFIX_SYMBOL(nud) {NULL, BP_NONE, nud, NULL, NULL}
SymbolBindRule Rules[] = {
/* TOKEN_LEFT_BRACE */ PREFIX_SYMBOL(mapLiteral),
}
//map对象字面量
static void mapLiteral(CompileUnit* cu, bool canAssign UNUSED) {
//本函数是'{'.nud(), curToken是key
//Map.new()新建map,为存储字面量中的"key->value"做准备
//先加载类,用于调用方法时从该类的methods中定位方法
emitLoadModuleVar(cu, "Map");
//再加载调用的方法,该方法将在上面加载的类中获取
emitCall(cu, 0, "new()", 5);
do {
//可以创建空map
if (PEEK_TOKEN(cu->curParser) == TOKEN_RIGHT_BRACE) {
break;
}
//读取key
expression(cu, BP_UNARY);
//读入key后面的冒号
consumeCurToken(cu->curParser,
TOKEN_COLON, "expect ':' after key!");
//读取value
expression(cu, BP_LOWEST);
//将entry添加到map中
emitCall(cu, 2, "addCore_(_,_)", 13);
} while (matchToken(cu->curParser, TOKEN_COMMA));
consumeCurToken(cu->curParser, TOKEN_RIGHT_BRACE, "map literal should end with \'}\'!");
}
对于上述map.nud方法,expression识别到左大括号后会执行,具体执行如下:
expression(当前token为左大括号"{") -> map.nud -> emitLoadModuleVar(Map类已经声明定义于模块变量,加载Map模块变量于栈顶)-> emitCall(调用Map的new方法,执行后一个Map实例会位于栈顶)-> expression(计算表达式key的值于栈顶)-> expression(计算表达式value的值于栈顶)-> emitCall(生成addCore_(,)指令) -> matchToken(匹配逗号)
对于本例子"Map.new()“,调用链如下:
expression(当前token为"Map”)-> id.nud(识别到Map为模块变量,加载Map的值于栈顶,这个值Value可以转成Class)-> "."的led方法 -> emitMethodCall(调用new方法生成空Map实例于栈顶)
addCore_(_ , _),Map类这种均属于核心类核心方法,对应第八章内容,下一篇博客总结。
6.3 编译 Map、List
6.3.1 “employeeTitle[“xh”] = “rd””
调用链都是很熟悉的函数,如下:
compileProgram -> compileStatment -> expression(当前token为employeeTitle) -> id.nud(employeeTitle被识别为模块变量,加载employeeTitle值于栈顶,这个值Value可以转化成Map实例ObjInstance) -> “[”.led(左中括号的led方法subscript)-> processArgList(加载中括号内实参即Employee实例于栈顶,只能加载一个实参。也就是执行expression然后执行id.nud,xh是一个模块变量,具体值是Employee实例转成Value) -> matchToken(匹配到等号) -> expression(计算等号后面的表达式,curToken为"rd")-> literal(识别为string,执行string的nud方法literal,也就是加载常量rd)-> emitLoadConstant(先把"rd"加载于常量表,再从常量表加载到栈顶) -> emitCallBySignature(加载等号右边表达式值后,根据SIGN_SUBSCRIPT_SETTER调用Map实例的subscript_setter方法)-> sign2String(根据前面生成方法名[ _ ]=()) -> OPCODE_CALL0 (先前已经入栈Map实例employeeTitle,key索引Employee实例xh,value字符串"rd",因此会在类employeeTitle中根据方法名[ _ ]=()的索引调用该方法)
Map的subscript_setter方法暂不分析。
SymbolBindRule Rules[] = {
/* TOKEN_LEFT_BRACKET */ {NULL, BP_CALL, listLiteral, subscript, subscriptMethodSignature},
}
//'['.led() 用于索引list元素,如list[x]
static void subscript(CompileUnit* cu, bool canAssign) {
//确保[]之间不空
if (matchToken(cu->curParser, TOKEN_RIGHT_BRACKET)) {
COMPILE_ERROR(cu->curParser, "need argument in the '[]'!");
}
//默认是[_],即subscript getter
Signature sign = {SIGN_SUBSCRIPT, "", 0, 0};
//读取参数并把参数加载到栈,统计参数个数
processArgList(cu, &sign);
consumeCurToken(cu->curParser,
TOKEN_RIGHT_BRACKET, "expect ']' after argument list!");
//若是[_]=(_),即subscript setter
if (canAssign && matchToken(cu->curParser, TOKEN_ASSIGN)) {
sign.type = SIGN_SUBSCRIPT_SETTER;
//=右边的值也算一个参数,签名是[args[1]]=(args[2])
if (++sign.argNum > MAX_ARG_NUM) {
COMPILE_ERROR(cu->curParser, "the max number of argument is %d!", MAX_ARG_NUM);
}
//获取=右边的表达式
expression(cu, BP_LOWEST);
}
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)));
}
}
6.3.2 Map的字面量创建方法、‘{’.nud()
var employeeHeight = {
"xh": 170,
"xm": 172,
"lw": 168,
"lz": 173
}
//map对象字面量
static void mapLiteral(CompileUnit* cu, bool canAssign UNUSED) {
//本函数是'{'.nud(), curToken是key
//Map.new()新建map,为存储字面量中的"key->value"做准备
//先加载类,用于调用方法时从该类的methods中定位方法
emitLoadModuleVar(cu, "Map");
//再加载调用的方法,该方法将在上面加载的类中获取
emitCall(cu, 0, "new()", 5);
do {
//可以创建空map
if (PEEK_TOKEN(cu->curParser) == TOKEN_RIGHT_BRACE) {
break;
}
//读取key
expression(cu, BP_UNARY);
//读入key后面的冒号
consumeCurToken(cu->curParser,
TOKEN_COLON, "expect ':' after key!");
//读取value
expression(cu, BP_LOWEST);
//将entry添加到map中
emitCall(cu, 2, "addCore_(_,_)", 13);
} while (matchToken(cu->curParser, TOKEN_COMMA));
consumeCurToken(cu->curParser, TOKEN_RIGHT_BRACE, "map literal should end with \'}\'!");
}
- 先加载模块变量Map(值是Class转过来的Value),然后调用Map的new静态方法,将空Map实例置于栈顶
- 循环matchToken匹配逗号和冒号,用expression计算key、value的值于栈顶,然后生成指令"addCore_(,)"将这对key、value加入Map
6.3.3 var allEmployee = [“xh”, “xm”, “lw”, “lz”]
//'['.nud() 处理用字面量形式定义的list列表
static void listLiteral(CompileUnit* cu, bool canAssign UNUSED) {
//进入本函数后,curToken是'['右面的符号
//先创建list对象
emitLoadModuleVar(cu, "List");
emitCall(cu, 0, "new()", 5);
do {
//支持字面量形式定义的空列表
if (PEEK_TOKEN(cu->curParser) == TOKEN_RIGHT_BRACKET) {
break;
}
expression(cu, BP_LOWEST);
emitCall(cu, 1, "addCore_(_)", 11);
} while (matchToken(cu->curParser, TOKEN_COMMA));
consumeCurToken(cu->curParser, TOKEN_RIGHT_BRACKET, "expect ']' after list element!");
}
- 先加载模块变量List(值是Class转过来的Value),然后调用List的new静态方法,将空List实例置于栈顶
- 循环matchToken匹配逗号,用expression计算值于栈顶,然后生成指令"addCore_(,)"将值加入List
7. 编译while
while (idx < count) {
System.print(allEmployee[idx])
idx = idx + 1
}
//编译while循环,如 while (a < b) {循环体}
static void compileWhileStatment(CompileUnit* cu) {
Loop loop;
//设置循环体起始地址等等
enterLoopSetting(cu, &loop);
consumeCurToken(cu->curParser,
TOKEN_LEFT_PAREN, "expect '(' befor conditino!");
//生成计算条件表达式的指令步骤,结果在栈顶
expression(cu, BP_LOWEST);
consumeCurToken(cu->curParser,
TOKEN_RIGHT_PAREN, "expect ')' after condition!");
//先把条件失败时跳转的目标地址占位
loop.exitIndex = emitInstrWithPlaceholder(cu, OPCODE_JUMP_IF_FALSE);
compileLoopBody(cu);
//设置循环体结束等等
leaveLoopPatch(cu);
}
//开始循环,进入循环体的相关设置等
static void enterLoopSetting(CompileUnit* cu, Loop* loop) {
//cu->fn->instrStream.count是下一条指令的地址,所以-1
loop->condStartIndex = cu->fn->instrStream.count - 1;
loop->scopeDepth = cu->scopeDepth;
//在当前循环层中嵌套新的循环层,当前层成为内嵌层的外层.
loop->enclosingLoop = cu->curLoop;
//使cu->curLoop指向新的内层
cu->curLoop = loop;
}
//编译循环体
static void compileLoopBody(CompileUnit* cu) {
//使循环体起始地址指向下一条指令地址
cu->curLoop->bodyStartIndex = cu->fn->instrStream.count;
compileStatment(cu); //编译循环体
}
//离开循环体时的相关设置
static void leaveLoopPatch(CompileUnit* cu) {
//获取往回跳转的偏移量,偏移量都为正数
int loopBackOffset = cu->fn->instrStream.count - cu->curLoop->condStartIndex + 2;
//生成向回跳转的CODE_LOOP指令,即使ip -= loopBackOffset
writeOpCodeShortOperand(cu, OPCODE_LOOP, loopBackOffset);
//回填循环体的结束地址
patchPlaceholder(cu, cu->curLoop->exitIndex);
//下面在循环体中回填break的占位符OPCODE_END
//循环体开始地址
uint32_t idx = cu->curLoop->bodyStartIndex;
//循环体结束地址
uint32_t loopEndindex = cu->fn->instrStream.count;
while (idx < loopEndindex) {
//回填循环体内所有可能的break语句
if (OPCODE_END == cu->fn->instrStream.datas[idx]) {
cu->fn->instrStream.datas[idx] = OPCODE_JUMP;
//回填OPCODE_JUMP的操作数,即跳转偏移量
//id+1是操作数的高字节,patchPlaceholder中会处理idx + 1和idx + 2
patchPlaceholder(cu, idx + 1);
//使idx指向指令流中下一操作码
idx += 3;
} else {
//为提高遍历速度,遇到不是OPCODE_END的指令,
//一次跳过该指令及其操作数
idx += 1 + getBytesOfOperands(cu->fn->instrStream.datas,
cu->fn->constants.datas, idx);
}
}
//退出当前循环体, 即恢复cu->curLoop为当前循环层的外层循环
cu->curLoop = cu->curLoop->enclosingLoop;
}
//用跳转到当前字节码结束地址的偏移量去替换占位符参数0xffff
//absIndex是指令流中绝对索引
static void patchPlaceholder(CompileUnit* cu, uint32_t absIndex) {
//计算回填地址(索引)
uint32_t offset = cu->fn->instrStream.count - absIndex - 2;
//先回填地址高8位
cu->fn->instrStream.datas[absIndex] = (offset >> 8) & 0xff;
//再回填地址低8位
cu->fn->instrStream.datas[absIndex + 1] = offset & 0xff;
}
//用占位符做为参数设置指令
static uint32_t emitInstrWithPlaceholder(CompileUnit* cu, OpCode opCode) {
writeOpCode(cu, opCode);
writeByte(cu, 0xff); //先写入高位的0xff
//再写入低位的0xff后,减1返回高位地址,此地址将来用于回填.
return writeByte(cu, 0xff) - 1;
}
CASE(JUMP_IF_FALSE): {
//栈顶: 跳转条件bool值
//指令流: 2字节的跳转偏移量
int16_t offset = READ_SHORT();
ASSERT(offset > 0, "OPCODE_JUMP_IF_FALSE`s operand must be positive!");
Value condition = POP();
if (VALUE_IS_FALSE(condition) || VALUE_IS_NULL(condition)) {
ip += offset;
}
LOOP();
}
CASE(LOOP): {
//指令流: 2字节的跳转正偏移量
int16_t offset = READ_SHORT();
ASSERT(offset > 0, "OPCODE_LOOP`s operand must be positive!");
ip -= offset;
LOOP();
}
CASE(JUMP): {
//指令流: 2字节的跳转正偏移量
int16_t offset = READ_SHORT();
ASSERT(offset > 0, "OPCODE_JUMP`s operand must be positive!");
ip += offset;
LOOP();
}
compileProgram -> compileWhileStatment -> enterLoopSetting(设置循环体起始地址loop->condStartIndex等,cu>curLoop置为loop) -> expression(生成条件判断指令)-> emitInstrWithPlaceholder(生成OPCODE_JUMP_IF_FALSE,用于条件失败后跳过循环体,具体跳转地址后面回填)-> compileLoopBody -> leaveLoopPatch(生成OPCODE_LOOP操作数为回跳偏移(即当前地址减起始地址+2),patchPlaceholder回填循环体的结束地址,给循环体所有break回填结束地址)
8 编译for循环
compileProgram -> compileStatment -> compileForStatment ->
//编译for循环,如 for i (sequence) {循环体}
static void compileForStatment(CompileUnit* cu) {
//其中sequence是可迭代的序列
//for循环会按照while循环的逻辑编译
// for i (sequence) {
// System.print(i)
// }
//
//在内部会变成:
// var seq = sequence
// var iter
// while iter = seq.iterate(iter) {
// var i = seq.iteratorValue(iter)
// System.print(i)
// }
//为局部变量seq和iter创建作用域
enterScope(cu);
//读取循环变量的名字,如"for i (sequence)"中"i"
consumeCurToken(cu->curParser,
TOKEN_ID, "expect variable after for!");
const char* loopVarName = cu->curParser->preToken.start;
uint32_t loopVarLen = cu->curParser->preToken.length;
consumeCurToken(cu->curParser,
TOKEN_LEFT_PAREN, "expect '(' befor sequence!");
//编译迭代序列
expression(cu, BP_LOWEST);
consumeCurToken(cu->curParser, TOKEN_RIGHT_PAREN, "expect ')' after sequence!");
//申请一局部变量"seq "来存储序列对象,
//其值就是上面expression存储到栈中的结果
uint32_t seqSlot = addLocalVar(cu, "seq ", 4);
writeOpCode(cu, OPCODE_PUSH_NULL);
//分配及初始化"iter ",其值就是上面加载到栈中的的NULL
uint32_t iterSlot = addLocalVar(cu, "iter ", 5);
Loop loop;
enterLoopSetting(cu, &loop);
//为调用"seq.iterate(iter)"做准备
//1 先压入序列对象"seq ", 即"seq.iterate(iter)"中的seq
writeOpCodeByteOperand(cu, OPCODE_LOAD_LOCAL_VAR, seqSlot);
//2 再压入参数iter,即"seq.iterate(iter)"中的iter
writeOpCodeByteOperand(cu, OPCODE_LOAD_LOCAL_VAR, iterSlot);
//3 调用"seq.iterate(iter)"
emitCall(cu, 1, "iterate(_)", 10);
//"seq.iterate(iter)"把结果(下一个迭代器)存储到
//args[0](即栈顶),现在将其同步到变量iter
writeOpCodeByteOperand(cu, OPCODE_STORE_LOCAL_VAR, iterSlot);
//如果条件失败则跳出循环体,目前不知道循环体的结束地址,
//先写入占位符.
loop.exitIndex = emitInstrWithPlaceholder(cu, OPCODE_JUMP_IF_FALSE);
//调用"seq.iteratorValue(iter)"以获取值
//1 为调用"seq.iteratorValue(iter)"压入参数seq
writeOpCodeByteOperand(cu, OPCODE_LOAD_LOCAL_VAR, seqSlot);
//2 为调用"seq.iteratorValue(iter)"压入参数iter
writeOpCodeByteOperand(cu, OPCODE_LOAD_LOCAL_VAR, iterSlot);
//3 调用"seq.iteratorValue(iter)"
emitCall(cu, 1, "iteratorValue(_)", 16);
//为循环变量i创建作用域
enterScope(cu);
//"seq.iteratorValue(iter)"已经把结果存储到栈顶,
//添加循环变量为局部变量,其值在栈顶
addLocalVar(cu, loopVarName, loopVarLen);
//编译循环体
compileLoopBody(cu);
leaveScope(cu); //离开循环变量i的作用域
leaveLoopPatch(cu);
leaveScope(cu); //离开变量"seq "和"iter "的作用域
}
consumeCurToken(消耗token"i") -> loopVarName、loopVarLen记录"i" -> consumeCurToken(消耗左括号)-> expression(计算序列对象,压入栈顶)-> consumeCurToken(消耗右括号)-> addLocalVar(添加局部变量"seq ",值为序列对象) -> OPCODE_PUSH_NULL -> addLocalVar(添加局部变量"iter ",值初始为NULL)-> enterLoopSetting (开始循环)-> OPCODE_LOAD_LOCAL_VAR(加载局部变量seq于栈顶) -> OPCODE_LOAD_LOCAL_VAR(加载局部变量iter于栈顶) -> emitCall(调用seq.iterate(iter)) -> OPCODE_STORE_LOCAL_VAR(将栈顶的值即seq.iterate(iter)的值赋值给iter,第一次这个值为索引0) -> emitInstrWithPlaceholder(ifelse占位符) -> OPCODE_LOAD_LOCAL_VAR(加载局部变量seq于栈顶) -> OPCODE_LOAD_LOCAL_VAR(加载局部变量iter于栈顶) -> emitCall(调用seq.iteratorValue(iter)) -> addLocalVar(将seq.iteratorValue(iter)至于栈顶的局部变量添加为局部变量 -> compileLoopBody
9. 编译if else
if (a.endsWith("s")) {
System.print(System.clock)
} else {
System.print("error!!!!!")
}
//编译if语句
static void compileIfStatment(CompileUnit* cu) {
consumeCurToken(cu->curParser, TOKEN_LEFT_PAREN, "missing '(' after if!");
expression(cu, BP_LOWEST); //生成计算if条件表达式的指令步骤
consumeCurToken(cu->curParser,
TOKEN_RIGHT_PAREN, "missing ')' before '{' in if!");
//若条件为假, if跳转到false分支的起始地址,现为该地址设置占位符
uint32_t falseBranchStart =
emitInstrWithPlaceholder(cu, OPCODE_JUMP_IF_FALSE);
//编译then分支
//代码块前后的'{'和'}'由compileStatment负责读取
compileStatment(cu);
//如果有else分支
if (matchToken(cu->curParser, TOKEN_ELSE)) {
//添加跳过else分支的跳转指令
uint32_t falseBranchEnd = emitInstrWithPlaceholder(cu, OPCODE_JUMP);
//进入else分支编译之前,先回填falseBranchStart
patchPlaceholder(cu, falseBranchStart);
//编译else分支
compileStatment(cu);
//此时知道了false块的结束地址,回填falseBranchEnd
patchPlaceholder(cu, falseBranchEnd);
} else { //若不包括else块
//此时falseBranchStart就是件为假时,需要跳过整个true分支的目标地址
patchPlaceholder(cu, falseBranchStart);
}
}
compileStatment -> compileIfStatment -> consumeCurToken(消耗左小括号) -> expression (生成条件判断指令)-> consumeCurToken(消耗RIGHT_PAREN)-> emitInstrWithPlaceholder(生成false跳转指令OPCODE_JUMP_IF_FALSE,以后回填)-> compileStatment -> matchToken(匹配到中括号) -> compileBlock(编译if代码块) -> 循环调用compileProgram -> matchToken(匹配TOKEN_ELSE)-> emitInstrWithPlaceholder(生成OPCODE_JUMP)-> patchPlaceholder(回填OPCODE_JUMP_IF_FALSE地址,跳过if )-> compileStatment (编译else代码块)-> patchPlaceholder(回填OPCODE_JUMP,跳过else)
10 编译return、break、continue、释放局部变量的理解
//编译return
inline static void compileReturn(CompileUnit* cu) {
if (PEEK_TOKEN(cu->curParser) == TOKEN_RIGHT_BRACE) { //空返回值
//空return,NULL做为返回值
writeOpCode(cu, OPCODE_PUSH_NULL);
} else { //有返回值
expression(cu, BP_LOWEST);
}
writeOpCode(cu, OPCODE_RETURN); //将上面栈顶的值返回
}
//编译break
inline static void compileBreak(CompileUnit* cu) {
if (cu->curLoop == NULL) {
COMPILE_ERROR(cu->curParser, "break should be used inside a loop!");
}
//在退出循环体之前要丢掉循环体内的局部变量
discardLocalVar(cu, cu->curLoop->scopeDepth + 1);
//由于用OPCODE_END表示break占位, 此时无须记录占位符的返回地址
emitInstrWithPlaceholder(cu, OPCODE_END);
}
//丢掉作用域scopeDepth之内的局部变量
static uint32_t discardLocalVar(CompileUnit* cu, int scopeDepth) {
ASSERT(cu->scopeDepth > -1, "upmost scope can`t exit!");
int localIdx = cu->localVarNum - 1;
//变量作用域大于scodeDepth的为其内嵌作用域中的变量,
//跳出scodeDepth时内层也没用了,要回收其局部变量.
while (localIdx >= 0 && cu->localVars[localIdx].scopeDepth >= scopeDepth) {
if (cu->localVars[localIdx].isUpvalue) {
//如果此局量是其内层的upvalue就将其关闭
writeByte(cu, OPCODE_CLOSE_UPVALUE);
} else {
//否则就弹出该变量回收空间
writeByte(cu, OPCODE_POP);
}
localIdx--;
}
//返回丢掉的局部变量个数
return cu->localVarNum - 1 - localIdx;
}
CASE(CLOSE_UPVALUE):
//栈顶: 相当于局部变量
//把地址大于栈顶局部变量的upvalue关闭
closeUpvalue(curThread, curThread->esp - 1);
DROP(); //弹出栈顶局部变量
LOOP();
//关闭在栈中slot为lastSlot及之上的upvalue
static void closeUpvalue(ObjThread* objThread, Value* lastSlot) {
ObjUpvalue* upvalue = objThread->openUpvalues;
while (upvalue != NULL && upvalue->localVarPtr >= lastSlot) {
//localVarPtr改指向本结构中的closedUpvalue
upvalue->closedUpvalue = *(upvalue->localVarPtr);
upvalue->localVarPtr = &(upvalue->closedUpvalue);
upvalue = upvalue->next;
}
objThread->openUpvalues = upvalue;
}
//编译continue
inline static void compileContinue(CompileUnit* cu) {
if (cu->curLoop == NULL) {
COMPILE_ERROR(cu->curParser, "continue should be used inside a loop!");
}
//回收本作用域中局部变量在栈中的空间, +1是指循环体(包括循环条件)的作用域
//不能在cu->localVars数组中去掉,
//否则在continue语句后面若引用了前面的变量则提示找不到
discardLocalVar(cu, cu->curLoop->scopeDepth + 1);
int loopBackOffset = cu->fn->instrStream.count - cu->curLoop->condStartIndex + 2;
//生成向回跳转的CODE_LOOP指令 即使ip -= loopBackOffset
writeOpCodeShortOperand(cu, OPCODE_LOOP, loopBackOffset);
}
//退出作用域
static void leaveScope(CompileUnit* cu) {
//对于非模块编译单元,丢弃局部变量
if (cu->enclosingUnit != NULL) {
//出作用域后丢弃本作用域以内的局部变量
uint32_t discardNum = discardLocalVar(cu, cu->scopeDepth);
cu->localVarNum -= discardNum;
cu->stackSlotNum -= discardNum;
}
//回到上一层作用域
cu->scopeDepth--;
}
CASE(RETURN): {
//栈顶: 返回值
//获取返回值
Value retVal = POP();
//return是从函数返回 故该堆栈框架使用完毕,增加可用堆栈框架数量
curThread->usedFrameNum--;
//关闭堆栈框架即此作用域内所有upvalue
closeUpvalue(curThread, stackStart);
//如果一个堆栈框架都没用,
//说明它没有调用函数或者所有的函数调用都返回了,可以结束它
if (curThread->usedFrameNum == 0) {
//如果并不是被另一线程调用的,就直接结束
if (curThread->caller == NULL) {
curThread->stack[0] = retVal;
//保留stack[0]中的结果,其它都丢弃
curThread->esp = curThread->stack + 1;
return VM_RESULT_SUCCESS;
}
//恢复主调方线程的调度
ObjThread* callerThread = curThread->caller;
curThread->caller = NULL;
curThread = callerThread;
vm->curThread = callerThread;
//在主调线程的栈顶存储被调线程的执行结果
curThread->esp[-1] = retVal;
} else {
//将返回值置于运行时栈栈顶
stackStart[0] = retVal;
//回收堆栈:保留除结果所在的slot即stackStart[0] 其它全丢弃
curThread->esp = stackStart + 1;
}
LOAD_CUR_FRAME();
LOOP();
}
case OPCODE_END:
//用于从当前及递归嵌套闭包时返回
return;
当break时,循环体自己这一层和比自己更深的嵌套作用域的局部变量需要调用discardLocalVar释放掉,由于enterLoopSetting在enterScope前面调用,所以传的是scopeDepth+1。如果是局部变量就是出栈,如果是upvalue就设置为closed upvalue(因为),关于upvalue和localVar的理解可以参考类定义博客的2.7.1.2 findVariable函数和2.8.6 endCompileUnit的分析。一个方法CU里面的LocalVar会保存自己作用域的局部变量,值是局部变量在栈内的索引,并记录自己的局部变量是否被某个嵌套作用域引用。cu->upvalues保存所有upvalue,值是自己直接外层LocalVa数组的索引或更外层upvalue数组的索引。
这里举一个例子:
假如我们当前模块作用域是A作用域,模块内的某类的方法作用域是B作用域,方法内有一个函数作用域C作用域,那么他们的scopeDepth显然C>B>A.
如果C作用域中使用了A作用域声明的变量,那么函数调用链如下:
id.nud(识别为局部变量) -> getVarFromLocalOrUpvalue -> findLocal (在C的直接嵌套作用域B中寻找B作用域的局部变量,当然是没找到返回-1) -> findUpvalue (在C作用域寻找upvalue) -> findLocal(在B作用域寻找局部变量,本例子显然没找到) -> findUpvalue (在B作用域寻找upvalue) -> findLocal (在A作用域寻找局部变量,显然会找到,返回该局部变量在A的CU->localVar数组的索引index,并且A的CU->localVar[index].isUpvalue置为True表示A作用域此局部变量被内层某作用域引用) -> addUpvalue(在B作用域的CU->upvalues中添加upvalue,保存的值为刚刚findLocal返回的索引,返回自己在CU->upvalues数组中添加的upvalue索引) -> addUpvalue (在C作用域CU->upvalues添加upvalue,保存刚刚addUpvalue返回的B作用域的upValue的索引,返回自己在CU->upvalues数组中添加的upvalue的索引)
总结一下就是:A的CU->localVar[index_A].isUpvalue置为True, B的CU->upvalues[index_B]保存index_A, C的CU->upvalues[index_C]保存index_B,index_A就是该局部变量在运行时栈的索引
然后一定要明确一个点,局部变量和upvalue的管理不是以作用域为单位,而是以编译单元为单位.所以上述例子为了举例是比较极端的例子,我们这个例子模块作用域manager.sp,函数作用域employeeInfo和while循环作用域中,有编译单元的只有模块和函数,所以while循环的局部变量和upvalue和保存在函数CU,但是while循环作用域是比函数作用域更深一层的作用域,因此他们的scopeDepth会大1
有了上述的例子,discardLocalVar释放局部变量就很好理解了:
假如需要break循环while作用域,那么while作用域的局部变量以及while作用域中可能被更内层嵌套作用域引用的局部变量都需要释放.
另外需要注意的点是:cu->localVar数组的索引本身就是局部变量在运行时栈的索引, cu->upvalues数组保存的是cu->localVar或cu->upvalues数组索引, 实际的局部变量值保存在运行时栈中.线程objThread->openUpvalues保存着本线程维护的函数, 每个编译单元在编译完成后会创建闭包,闭包中有编辑单元的指令流和环境upvalues,当执行闭包的指令时,线程objThread会给该闭包分配一个框架, 给闭包使用一小块运行时栈. 当运行初始模块闭包时, 不需要记录初始模块CU的upvalues,因为第一个模块脚本执行完毕那么os层的线程或进程就结束了, 线程/进程自然会被操作系统回收.当线程执行第一个初始脚本模块时,假如模块内有一个函数, 那么函数编译完成后会调用endCompileUnit像自己父编译单元创建函数名常量和创建闭包指令, 因此执行初始脚本模块的指令流时一定会执行到创建闭包指令来给这个函数创建闭包, 创建闭包时调用createOpenUpvalue会把该函数CU的所有upvalue保存在线程objThread->openUpvalues, 按upvalue对应的局部变量在运行时栈的内存地址降序排列.
具体见博客类定义2.8.6节endCompileUnit以及博客虚拟机