自制编程语言基于c语言实验记录之三:总结三四五六七章之编译标识符

博客前言

本篇博客是接着上一次博客的,上一篇博客编译了类中变量方法,但是这只是声明而已,并没有实际使用,本篇博客就是列举了当编译实际使用变量调用方法语句时,函数的执行顺序,这与上篇博客密切相关,因为上篇博客完成的编译类中变量方法等,实际是在编译单元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}

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时,

  1. 首先curtoken为2,识别到curtoken.type为num,故num的nud方法为literal,literal方法将该number存入常量表并生成了将常量从常量表加载至栈的指令。
  2. 然后curtoken变成+,然后执行while判断,+的绑定能力显然大于0,所以进入while循环体。
  3. 由Rules得知,+的led方法是infixOperator,infixOperator中将+的绑定权值传入expression,递归执行expression,同时此时的curtoken已经成为了3。
  4. 执行expression时,curtoken为3,3的nud方法仍为literal,同时3的绑定权值小于+的绑定权值,所以不会进入while循环体,故这层expression退出,回到+的led中。
  5. +的led,infixOperator执行完expression后,生成了根据加法签名生成了调用加法方法指令。
  6. +的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。

  1. getEnclosingClassBK获得classBK,即new方法的cu的->enclosingUnit->enclosingClassBK,在上篇博客的compileClassDefinition可以找到cu->enclosingClassBK的赋值,这里就可以看出enclosingClassBK的重要,用于跟踪正在编译的类,里面存储着该类的实例域信息。classBK不等于NULL说明当前传入id的cu要么是模块cu正在编译类中语句,要么是类中方法cu正在编译类中方法语句。这里的例子显然是后者
  2. getIndexFromSymbolTable从classBK->fields找实例域,与编译类定义实例域博客呼应
  3. 如果发现等号,则调用expression生成指令将等号右边值计算出来放置栈顶
  4. 在实例的fields加载(将实例域push于栈顶)或者存储(赋值实例的fields)

这里再总结一下编译father 等实例变量的函数顺序。结合了之二和之三博客列举的函数:
compileClassDefinition -> compileClassBody ->compileMethod ->compileBody ->compileBlock->compileProgram->compileStatment->expression-> id.nud方法(由于后面是等于,所以canAssign为True)->matchToken(cu->curParser, TOKEN_ASSIGN)->expression计算"f"即将f加载入栈,注意这里的f是形参,上篇博客的编译方法函数里调用了idMethodSignature处理生成方法签名并调用processParaList处理形参(即局部变量),processParaList里调用declareVariable里面的declareLocalVar定义为了该方法CU里的局部变量,处理形参本质是声明为局部变量,而为该方法局部变量赋值显然在该方法的调用可以见2.3节,id如何处理方法调用标识符的->STORE_THIS_FIELD->PEEK栈顶的f复制到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();
      }

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,步骤如下:

  1. classBK不等于NULL说明当前传入id的cu要么是模块cu正在编译类中语句,要么是类中方法cu正在编译类中方法语句。这里的例子显然是后者
  2. 静态变量"cls 静态域属性名 name"存储在模块变量的LocalVar中,实际值存储在运行时栈因为充当模块局部变量,被所有对象共享。调用getVarFromLocalOrUpvalue在模块cu-LocalVar里寻找索引时,上述例子必然找不到。故调用findUpvalue想外层CU继续寻找cu->LocalVar,显然外层CU是模块CU,所以findUpvalue的第一次findLocal就会找到同时加入方法CU->upvalue
  3. 调用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() {
		a = 10 
		pr(a) 
 	}
 	pr(num) { 
 		System.print(num) 
	)
}

类名即模块变量必须大写,否则编译器会把标识符按照方法来处理,方法名是小写
以上述例子来举例,编译new方法的时候(上一篇博客),编译语句"pr(a)",这显然是一个方法,会调用id来识别pr这个标识符完成方法调用功能,当然也会完成实参赋值形参,具体执行顺序如下:
compileClassDefinition -> compileClassBody -> compileMethod -> compileBody -> compileBlock -> compileProgram -> compileStatment -> expression -> id.nud方法处理pr(由于后面是等于,所以canAssign为True)-> emitMethodCall ->processArgList -> expression -> id.nud处理实参a(curtoken为a) -> id函数里的getVarFromLocalOrUpvalue获取在2.1节的processParaList声明的局部变量索引 -> emitLoadOrStoreVariable根据索引生成存储或加载指令,这里的例子由于是等于赋值显然生成存储指令。-> emitCallBySignature根据方法签名,在vm->allMethodNames里找到对应方法索引(由上篇博客编译方法提供),生成调用方法指令OPCODE_CALL0+sign->argNum

总结:所以实参赋值已经声明的形参局部变量是在id函数里的局部变量处理部分,
完成方法调用功能是emitMethodCall函数的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生成读取指令
   }
}

你可能感兴趣的:(编译器,c语言,linux,开发语言)