编译2

Posted megachen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译2相关的知识,希望对你有一定的参考价值。

4 编译2

编译

  1. 编译阶段主要就是生成指令, 打一个比方, 现在有一个项目, 领导为了完成这个计划, 将这个计划按照几个步骤划分(就是指令), 领导就是用这些指令指定了计划的运行流程, 但是项目具体怎么落实是交给员工的, 员工得到了这个指令, 怎么执行就让员工自己来完成, 比如, BUY_FOOD指令, 员工执行此指令, 就可能开车去超市买菜, 也可能骑自行车去等等
  2. 函数签名指的是方法名+参数列表, 所以生成函数调用指令签名很重要
  3. 对于函数, 类等特殊变量采用特殊的编译方法, 也就是把名字改一改罢了, 如果一个函数名为test, 那么编译器将其编译成Fn test, 空格用户是输入不了的, 我们这样一搞就独一无二了
  4. processArgList与expression, 在有多个表达式, 并且使用‘,‘分隔的时候使用processArgList, 在是有一个表达式的时候使用expression, 很好的例子就是解析a[1] = 2时, 会编译成a.[args[1]]=(args[2]), [1]中使用processArgList, =右边使用expression
  5. 在符号的nud和led方法中, 如果该符号可以被赋值, 那么该nud或者led方法参数中需要bool canAssign
  6. expression等将结果保存到了栈顶, 如果我们要调用的是一个函数, 则还需要调用emitCall之类的方法获取栈顶元素进行方法调用
  7. 基本上所有的运算符都被称之为operator, 但是&&, ||, ?:等不是操作符, 他们可以理解为一个if-else, 他们是和num, string一样的符号
  8. 一个类中使用static修饰的静态变量是编译单元的局部变量
  9. localVar指的是Value值的name

温习编译模块设涉及到的结构体

  1. LocalVar

typedef struct localvar{
    const char *name;
    uint32_t length;
    bool isUpvalue; // 是不是upvalue
    int scopeDepth;
} LocalVar;
  1. Upvalue

typedef struct upvalue {
    bool isEnclosingLocalVar; // 如果为true, 则表示index索引的是父编译单元的localVars, 如果为false, 则表示索引的是父编译单元的upvalues, NB啊
    uint32_t index;
} Upvalue;
  1. ClassBookKeep

typedef struct classbookkeep {
    ObjString *name;
    SymbolTable fields;
    bool isStatic;
    IntBuffer instantMethods;
    IntBuffer staticMethods;
    Signature *sign;
} ClassBookKeep;
  1. Loop

typedef struct loop {
    int condStartIndex;
    int bodyStartIndex;
    int exitIndex;
    int scopeDepth;
    struct loop *enclosingLoop;
} Loop;
  1. CompileUnit

typedef struct compileunit {
    ObjFn *fn;
    
    LocalVar localVars[128];
    uint32_t localVarNum;
    
    Upvalue upvalues[128];
    uint32_t upvalueNum;
    
    Loop *curLoop;
    ClassBookKeep *ClassBK;
    
    uint32_t stackSlotNum;
    
    int scopeDepth;
    struct compileunit *enclosingCompileUnit;
} CompileUnit;
  1. SymbolBindRule

typedef struct symbolbindrule {
    const char *id;
    BindPower lbp;
    DenotationFn nud;
    DenotationFn led;
    MethodSignature methodSign;
} SymbolBindRule;

CompileUnit方法(最重要的是关于语法分析(TDOP)的方法, 就是各个符号的nud与led方法)

关于局部变量

  1. findLocal, 查找局部变量, 返回下标
  2. addUpvalue, 添加upvalue, upvalue的值主要是isEnclosingLocalVar和index
  3. findUpvalue, 查找upvalue, 在父编译单元和之上查看upvalues属性, 参数为局部变量的名称
  4. getVarFromLocalOrUpvalue, 对findLocal和findUpvalue统一封装
  5. isLocalName, 本编程语言规定, 小写字母开头的为局部变量

写指令(原子性)

  1. writeByte, 是所有写指令的基础哈
    0.5. writeOpCode, 写入一个操作码, 但是该操作码没有操作数
  2. writeOpCodeByteOperand, 写入一个操作码, 一个字节的操作数到cu->fn->instrStream
  3. writeOpCodeShortOperand, 写入一个操作码, 两个字节的操作数到cu->fn->instrStream
  4. emitLoadConstant, 添加常量到符号表中, 并生成LOAD_CONSTANT index指令
  5. emitCallBySignature(基本上有参数的都会调用该方法), 将方法签名添加到符号表中, 并生成CALLX index的指令, bySignature的意思就是参数是Signature, 这样是为了区别emitCall的参数
  6. emitCall, 与emitCallBySignature类似, 但是此方法更加高层, 它要call的方法签名已经被添加到符号表中了
  7. processArgList, 主要是针对方法调用时的形参是一个表达式的时, 需要计算, 那就需要调用expression方法计算表达式的结果
  8. emitLoadVariable, 生成把变量var加载到栈的指令
  9. emitStoreVariable, 为变量var生成存储的指令
  10. emitLoadThis, 生成将this加载到栈的指令

写指令(宏观性)

  1. compileBlock
  2. compileBody, 内部会调用compileBlock, 也就是处理完block{}之后, 处理一下返回值, 如果是构造方法, 则调用emitLoadThis将this加载到栈, 作为返回值, 也就是压栈
  3. endCompileUnit, 第一步写入OPCODE_END指令标志一个编译单元结束, 接着调用将当前cu的fn添加到父编译单元的constants中作为常量, 接着生成创建闭包的指令CREATE_CLOSURE, 因为我们的结构是内层函数以闭包的形式存在在外层函数中, 就在上一句我们将当前的cu的fn添加到外层编译单元的constants中, 这个过程就是闭包, 除此之外, 内层函数可能引用到了外层函数局部变量, 所以还要生成写入upvalues的指令, 既然编译完毕了, 则返回真正有用的cu->fn
  4. emitGetterMethodCall, 生成getter或一般method调用指令, 4和5核心是调用emitCallBySignature或者其他的, 反正就是封装了
  5. emitMethodCall, 生成方法调用指令, setter与getter, emitMethodCall与emitGetterMethodCall需要接受一个OpCode参数, 其实一开始我在怀疑这样不是多此一举么, emitMethodCall与emitGetterMethodCall他们的OpCode不都是OPCODE_CALL0起头的么, 后来才明白, super的指令也是通过emitMethodCall与emitGetterMethodCall生成的, 现在有了两个指令, 一个是OPCODE_CALLX, 另外一个就是OPCODE_SUPERX, 所以要传入参数OpCode
  6. emitInstrWithPlaceholder, 接受一个指令的参数, 一般为OPCODE_JUMP或者OPCODE_JUMP_IF_FLASE, 操作码原样写, 但是操作数采用0xffff填充两个字节(大段字节序), 到时候使用patchPlaceholder方法回填0xffff

static void processArgList() {
    do {
        // expression会将是表达式的实参的计算过程的指令写入到instStream流中, 这里到时候VM执行的时候就可以处理实参了
        this->expression(BP_LOWSET);
    } while (this->curParser->matchToken(TOKEN_COMMA))
}

存符号

  1. declareModuleVar, 声明一个模块变量, 如类
  2. declareVariable, 声明一个变量, 该变量可能是全局变量(模块变量), 也可能是局部变量,需要在该函数中进行判别, 接着添加到符号表中, 如果是模块变量调用declareModuleVar方法添加到模块的符号表中, 如果是局部的, 则添加到CompileUnit的符号表中
  3. processParaList, 将方法的形参(也就是参数的声明)添加到符号表中

static void processArgList(Signature *sign) {
    do {
        this->curParser->consumeToken(TOKEN_ID);
        this->declareVariable(this->curParser->preToken.str);
    } while(this->curParser->matchToken(TOEKN_COMMA));
}

创建方法标签

  1. 单目运算符

static void unaryMethodSignature(Signature *sign) {
    // 方法名已经解析了, 所有只需要改变类型
    sign->type = SIGN_GETTER;
}
  1. 中置运算符

static void infixMethodSignature(Signature *sign) {
    sign->type = SIGN_METHOD;
    this->curParser->consumeToken(TOKEN_LEFT_PEREN, "expect (");
    this->curParser->consumeToken(TOKEN_ID, "expect id");
    this->declareVariable(this->curParser->curToken.str);
    this->curParser->consumeToken(TOKEN_RIGHT_PEREN, "expect )");
}
  1. 既可以是前置又可以是中置运算符的方法签名

static void mixMethodSignature(Signature *sign) {
    // 该方法就是对上面的unaryMethodSignature和infixMethodSignature的封装吧了
    // 就是两种情况都考虑进去
    
    // 默认的情况为xxx, 此为getter形式的
    sign->type = SIGN_GETTER;
    if (!this->curParser->matchToken(TOKEN_ASSIGN)) {
        // 后面是不=, 那就是getter了
        return;
    }
    
    this->curParser->consumeToken(TOKEN_LEFT_PAREN);
    sign->type = SIGN_METHOD;
    sign->argNum = 1;
    /* 注意: 这里最好不要调用processList方法, 该方法是转为给普通方法使用的, 而不是符号方法, 在这里我们使用declareVariable这种底层的方法处理, 还有不同方法是通过, 分别参数个数的, 我们这里的符号方法参数最多就1个 */
    // this->processParaList(sign);
    this->curParser->consumeToken(TOKEN_ID);
    this->declareVariable(this->curParser->preToken.str);
    this->curParser->consumeToken(TOKEN_RIGHT_PAREN);
}
  1. 标识符版本的方法
// 标识符版本的方法包括, setter, getter, subscript, subsriptsetter, method, construct
static void idMethodSignature(Signature *sign) {
    sign->type = SIGN_METHOD;
    // 构造方法new->Person.new(_, ...)
    if (sign->name == "new") {
        if (this->curParser->matchToken(TOKEN_ASSIGN)) {
            COMPILE_ERROR
        }
        if (!this->curParser->matchToken(TOKEN_LEFT_PAREN)) {
            COMPILE_ERROR
        }
        sign->type = SIGN_CONSTRUCT;
        if (this->curParser->matchToken(TOKEN_RIGHT_PAREN)) {
            // 无参数直接返回
            return;
        }
    } else { // 是其余的方法
        // 1. 处理setter情况
        // 2. 处理getter
        // 3. 处理普通method
        // 解决这3种情况即可
    }
    // 注意: 函数是有参数的, 在最后要处理参数问题, 在这里仅仅是声明参数(添加到符号表中)
    this->processParaList(sign);
    this->curParser->consumeToken(TOKEN_RIGHT_PAREN, "expect )");
}
  1. 下标操作符[编译签名

static void subscriptMethodSignature(Signature *sign) {
    sign->type = SIGN_SUBSCRIPT;
    cu->processParaList(sign);
    cu->curParser->consumeToken(TOKEN_RIGHT_BRACKET, "expect ‘]‘ after index list!");
    trySetter(cu, sign);
}

语法分析

  1. expression, javascript中的TDOP的expression返回的是一个树, 但是我们这里是void, 因为我们的脚本语言不使用树, 而是通过expression方法可以计算出结果的指令, 到时候交给VM执行就可以得出结果了

static void expression(BindPower rbp) {
    SymbolBindRule *rule = this->curParser->curToken.getRule();
    this->curParser->getNextToken();
    rule->nud(this);
    rule = this->curParser->curToken.getRule();
    while (rbp < rule->lbp) {
        this->curParser->getNextToken();
        rule->led(this);
    }
}

SymbolBindRule方法

nud方法

  1. 字面量(数字与字符串)的nud, 将id添加到符号表中, 并生成对应的LOAD_CONSTANT index指令, 一开始遇到这种情况的时候, 我在想为什么还要生成对应的LOAD_CONSTANT index指令, 指令不是来让VM读取并执行的么, 其实这一句话就是关键, 语法分析的过程不是声明的过程, 而是定义, 如1 + 2, 这一句其实就是1.+(2), 这是一个方法调用, 显然我们对其要生成指令交给虚拟机指令, 这就是为什么需要将id添加到符号表中之后还要生成对应的LOAD_CONSTANT index指令
  2. // 字面量.nud()方法
    static void literal(CompileUnit *cu) {
        // 加载到符号表中
        uint32_t index = cu.addConstant();
        // 生成指令
        cu.writeOpCodeShortOperand(index);
    }
  3. 前置运算符(单元运算符), 前置运算符没有左操作数, 所以不关心左操作数, 所有有nud方法但是没有led方法, 在该nud方法中需要生成callX指令, 因为在面向对象的语言中, 运算符也是特殊的方法

static void unaryOperator(CompileUnit *cu) {
    cu->expression(BP_UNARY);
    // 生成0个参数, 方法名为this->id的签名, 这里不需要调用emitCallBySignature, 因为0个参数的方法签名就是方法名
    cu->emitCall(0, this->id);
}
  1. id(变量名与函数名等标识符的nud()方法, 这里将变量名与函数名放在一起, 是为了说明他们都是对象)

static void id(CompileUnit *cu, bool canAssign) {
    // id包括函数名与变量名, 而变量名又有很多种, 我们按照如下的方法判断
    // 函数名->局部变量名->实例变量名->静态变量名->模块变量名(包括模块变量与模块函数)
    Token name = cu->curParser->preToken; // 是id名
    // 为函数名, 首先当前的编译单元要为模块编译单元
    if (cu->enclosingUnit == NULL && cu->curParser->matchToken(TOKEN_LEFT_PAREN)) {
        name.str = "Fn " + name.str;
        Variable var;
        var.scopeType = VAR_SCOPE_MODULE;
        // 函数在模块的varName中是Fn funcName保存的
        var.index = cu->getIndexFromSymbolTable(&cu->curParser->curModule->moduleVarNames, id);
        // 没有定义该函数, 报编译错误
        if (var.index == -1) {
            COMPILE_ERROR(cur->curParser, "function %s is undefined!", name.str[3:]);
        }
        // 如果该函数在之间已经定义过了, 则现将该函数(闭包)压栈, 也就是生成压栈的指令
        // 为什么要将其压栈, 因为我们把函数也看成了对象了, 对象有一个call方法, 我们会调用它的call方法
        cu->emitLoadVariable(var);
        
        // 下面会将函数调用编译成如下形式
        // Fn functionName.call(_, ...)
        // 显然, 这时call成了方法名, 而我们调用的方法成了一个对象, 这个和Python的思想是一样的
        Signature sign;
        sign.name = "call";
        sign.argNum = 0;
        if (!cu->curParser->matchToken(TOKEN_RIGHT_PAREN)) {
            cu->processArgList(&sign); // 内部会生成计算出实参的表达式
            cu->curParser->consumeToken(TOKEN_RIGHT_PAREN, "expect ‘)‘ after argument list!");        
        }
        // 调用call方法
        cu->emitCallBySignature(&sign);
    } else {
        Variable var = cu->getVarFromLocalOrUpvalue(name.str);
        // 找到了
        if (var.index != -1) {
            // 是读取还是保存就交给emitLoadOrStoreVariable方法
            cu->emitLoadOrStoreVariable(var);
            return;
        }        
        // 实例变量
        if (cu->enclosingClassBK != NULL) {
            int fieldIndex = cu->getIndexFromSymbolTable(&cu->enclosingClassBK->fields, name.str);
            // 在class的实例中找到了该域
            if (fieldIndex != -1) {
                bool isRead = true;            
                if (cu->curParser->matchToken(TOKEN_ASSIGN) && assign) {
                    isRead = false;
                    cu->expression(BP_LOWEST);
                }
                
                if (cu->enclosingUnit != NULL) {
                    cu->writeOpCodeByteOperand(isRead ? OPCODE_LOAD_THIS_FIELD : OPCODE_STORE_THIS_FIELD);
                } else {
                    cu->emitLoadThis();
                    cu->writeOpCodeByteOperand(isRead ? OPCODE_LOAD_FIELD : OPCODE_STORE_FIELD);
                }
                return;
            }
        }
        
        // 按照静态域查找
        if (cu->enclosingClassBK != NULL) {
            string *staticFieldName = "Cls " + cu->enclosingClassBK->name.str + " " + name.str;
            Variable var = cu->getVarFromLocalVarOrUpvalue(staticFieldName);
            // 找到了
            if (var.index != -1) {
                cu->emitLoadOrStoreVariable(var, canAssign);
                return;
            }
        }
    }
    // 为模块变量
    var.scopeType = VAR_SCOPE_MODULE;
    var.index = cu->getIndexFromSymbolTable(name.str);
    // 没有找到模块变量, 那就在看一个是不是函数名
    if (var.index == -1) {
        string name = "Fn " + name.str;
        var.index = cu->getIndexFromSymbolTable(&cu->curParser->curModule->moduleNames, name);
        // 还是没有找到
        if (var.index == -1) {
            // 先声明一下, 对模块变量放宽限制
            cu->declareModuleVar(cu->curParser->curModule, name, NUM_TO_VALUE(cu->curParser->lineNo));
        }
    }
    cu->emitLoadOrStoreVariable(var, canAssign);
}
  1. bool类型的nud()

static void boolean() {
    Token name = this->curParser->preToken;
    OpCode opCode = name.type == TOKEN_TRUE ? OPCODE_PUSH_TRUE : OPCODE_PUSH_FALSE;
    this->writeOpCode(opCode);
}
  1. null的nud()

static void null() {
    cu->writeOpCode(OPCODE_PUSH_NULL);
}
  1. this的nud()

static void this() {
    this->emitLoadThis();
}
  1. super的nud()

static void super() {
    // 先判断是否在class中
    if (cu->enclosingClassNK == NULL) {
        COMPILE_ERROR(cu->curParser, "super should be used in a class");
    }
    
    // 1. super出现的第一种形式, super.methodName()
    // 如果当前的Token为.号
    if (cu->curParser->matchToken(TOKEN_DOT)) {
        // 这个时候是不需要考虑this的, 之后再一个方法的内部才考虑, 而我们现在这里只是一个方法调用的语句
        cu->consumeToken(TOKEN_ID, "expect id after ‘.‘");
        cu->emitMethodCall(cu->curParser->preToken.name, OPCODE_SUPER0);
    } else {
        // super():
        emitGetterMethodCall(cu, cu->enclosingCalssBK->signature, OPCODE_SUPER0);
    }
}
  1. (号的nud()方法

static void parentheses() {
    // (右边一定是一个表达式
    cu->expression(BP_LOWEST);
    cu->consumeToken(TOKEN_RIGHT_PAREN, "expect ‘)‘ after expression!");
}
  1. [号的nud()方法, [是mix类型的符号, 既会有nud(), 又会有led(), 要看具体情况, 在list字面量表达的时候就是nud()方法
// listLiteral的字面量, 也就是一个list对象, 所有在该nud()方法中我们应该生成创建list对象, 并且调用方法的指令
static void listLiteral() {
    // 定义list对象
    cu->emitLoadModuleVar("List");
    // 调用new()构造方法
    cu->emitCall("new()");
    do {
        // 遇到了一个]结束
        if (PEEK_TOKEN(cu->curParser) == TOKEN_RIGHT_BRACKET) {
            break;
        }
        // 处理一下表达式
        cu->expression(BP_LOWEST);
        cu->emitCall("addCore_(_)");
    } while (cu->matchToken(TOKEN_COMMA));
    cu->curParser->consumeCurToken("expect ‘]‘");
}
  1. {号的nud()方法, 代表的是Map的字面量

static void mapLiteral() {
    // 与list的字面量一样, 先加载Map class模块变量, 创建map对象
    // 在当前parser处理的模块中查找的Map并放到栈顶
    this->emitLoadModuleVar("Map");
    this->emitCall("new()", 0);
    do {
        if (PEEK_TOKEN(this->curParser) == TOKEN_RIGHT_BRACE) {
            break;
        }
        // key
        this->expression(BP_LOWEST);
        // :
        this->curParser->consumeCurToken(TOKEN_COLON, "expect ‘:‘ after key");
        // value
        this->expression(BP_LOWEST);
        this->emitCall("addCore_(_,_)", 2);
    } while (this->curParser->matchToken(TOKEN_COMMA));
    this->curParser->consumeCurToken(TOKEN_RIGHT_BRACE, "expect ‘}‘ to present map");
}

led方法

  1. 中置运算符

static void infixOperator(CompileUnit *cu) {
    cu->expression(this->lbp);
    // 中置运算符需要一个方法签名, 因为他是有参数的, 参数名为this->id, 参数个数为1
    Signature sign = {SIGN_METHOD, this->id, 1};
    cu->emitCallBySignature(sign);
}
  1. [号的led, 在最为subscript的时候, a[1] = 100
// 下标有两种形式, 一种是getter, 另外一种是setter
static void subscript(CompileUnit *cu, bool canAssign) {
    // getter时
    // []里面不能为空
    if (cu->curParser->matchToken(TOKEN_RIGHT_BRACKET)) {
        COMPILE_ERROR(cu->curParser, "[] cannot be empty");
    }
    // []是方法, 所以里面是参数, 我们调用processArgList
    Signature sign = {SIGN_SUBSCRIPT, "", 0, 0};
    cu->expression(BP_LOWEST);
    cu->processArgList(&sign);
    cu->consumeCurToken(TOKEN_RIGHT_BRACKET, "expect ‘]‘!");
    
    // 如果后面还有=
    if (cu->matchToken(TOKEN_ASSIGN) && canAssign) {
        sign.type = SIGN_SUBSCRIPT_SETTER;
        cu->expression(BP_LOWEST);
    }
    cu->emitCallBySignature(&sign, OPCODE_CALL0);
}
  1. 特殊标识符的led()方法
    • &&, ||与if-else在实质上一样的, 他们两个分支都会被编译, 采用回填技术完成后置编译, 所以需要有专门的填坑与补坑的方法, emitInstrWithPlaceholder和patchPlaceholder
    • emitInstrWithPlaceholder方法

      // OpCode应为与jump或则jump_if_false
      static uint32_t emitInstrWithPlaceholder(OpCode) {
          this->writeOpCode(OpCode);
          this->writeByte(0xff);
          return this->writeByte(0xff) - 1; // 返回到高地址地址
      }
    • patchPlaceholder方法

      
      // absIndex就是一开始emitInstrWithPlaceholder返回到的值
      static void patchPlaceholder(uint32_t absIndex) {
          // 为什么还要-2, 我现在也不清楚
          uint32_t offset = this->fn->instrStream.count - absIndex - 2;
          this->fn->instrStream.datas[absIndex] = (offset >> 8) & 0xff
          this->fn->instrStream.datas[absIndex + 1] = offset & 0xff;
      }
    1. &&.led()
    // expression1 && expression2
    static void logicAnd() {
        // 现在在expression2
        uint32_t index = this->emitInstrWithPlaceholder(OPCODE_AND);
        this->expression(BP_LOGIC_AND);
        this->patchPlaceholder(index);
    }
    1. ||.led()

      
      static void logicOr() {
          uint32_t index = this->emitInstrWithPlaceholder(OPCODE_OR);
          this->expression(BP_LOGIC_OR);
          this->patchPlaceholder(index);
      }
    2. "? :".led()

      
      static void condition() {
          uint32_t falseStart = this->emitInstrWithPlaceholder(OPCODE_JUMP_IF_FLASE);
          // 编译true分支
          this->expression(BP_LOWEST);
          // 便已完毕true分支就知道了false分支的位置了
          this->patchPlaceholder(falseStart);
          uint32_t falseEnd = this->emitInstrWitPlaceholder(OPCODE_JUMP);
          this->expression(BP_LOWEST);
          this->patchPlaceholder(falseEnd);
      }

既可以是前置又可以中置的运算符

  1. 要合理的选择上面提到的nud和led的组合

编译变量定义

  • 变量的定义分为静态域定义, 实例域定义, 局部变量定义和模块变量定义, 我们知道当一个CompileUnit编译模块的时候, 模块变量不在他的localVars中, 而是在对应的模块的符号表中, 但是我们不能空留着localVars, 所以如果正在编译一个类的时候, 但是还没有编译方法的时候, 将定义的静态域变量存放到该cu的localVars, 而实例变量是存放在classBK的fields符号表中
  • compileVarDefinition实现

static compileVarDefinition(bool isStatic) {
    this->curParser->consumeCurToken(TOKEN_ID, "expect ‘name‘ after var keyword!");
    Token name = cu->curParser->preToken;
    // 当前编译的是class
    if (this->enclosingUnit == NULL 
        && this->enclosingClassBK != NULL) {
        // 当前编译的是静态域
        if (isStatic) {
            string staticFieldId = "Cls " + this->classBK->name + " " + name.str;
            // staticFieldId是存放在cu的localVars中的
            // 查找看是否重复定义了
            // 之前没有定义
            if (this->findLocal(staticFieldId) == -1) {
                // 静态域默认值为NULL
                this->writeOpCode(OPCODE_PUSH_NULL);
                uint32_t index = this->declareVariable(staticFieldId);
                this->defineVariable(index);        
                // 静态域可以初始化
                Variable var = this->findVariable(staticFieldId);
                if (this->curParser->matchToken(TOKEN_ASSIGN)) {
                    this->expression(BP_LOWEST);
                    this->emitStoreVariable(var);
                }
            } else {
                COMPILE_ERROR(cu->curParser, "%s is redefined!", name.str);
            }    
        } else {
            // 在实例域中查找
            int fieldIndex = this->classBK->fields->getIndexFromSymbolTable(name.str);
            if (fieldIndex == -1) {
                fieldIndex = this->classBK->fields->addSymbol(name.str);        
            } else {
                COMPILE_ERROR(this->curParser, "%s redefined!", name.str);
            }
            return;
        }
    }
    
    // 其他类型变量的定义
    if (this->curParser->matchToken(TOKEN_ASSIGN)) {
        var a = 1 + 1
        this->expression(BP_LOWEST);
    } else {
        // var a
        this->writeOpCode(OPCODE_PUSH_NULL);
    }
    // declare是添加符号
    uint32_t index = this->declareVariable(name.str);
    // define生成value相关的指令, localVars与栈中的局部变量布局一致
    this->defineVariable(index);
}

以上是关于编译2的主要内容,如果未能解决你的问题,请参考以下文章

导致资产预编译在heroku部署上失败的代码片段

如何有条件地将 C 代码片段编译到我的 Perl 模块?

损坏的顶点和片段着色器

Android 逆向Android 逆向通用工具开发 ( Android 平台运行的 cmd 程序类型 | Android 平台运行的 cmd 程序编译选项 | 编译 cmd 可执行程序 )(代码片段

C程序存储结构

错误记录Android Studio 编译报错 ( Could not determine java version from ‘11.0.8‘. | Android Studio 降级 )(代码片段