从零到一构建C语言解释器-CPC源码

文章目录

  • 参考
  • 框架
  • 设计
  • vm指令集
  • 分配空间
  • 词法分析
  • 语法分析
  • 递归下降
  • 表达式
  • 优先级爬山

参考

https://lotabout.me/2015/write-a-C-interpreter-1/
https://github.com/archeryue/cpc
https://www.bilibili.com/video/BV1Kf4y1V783/?vd_source=a1be939c65919194c77b8a6a36c14a6e

框架

工业级别编译器分前端和后端

前端(parser):源代码->中间代码(IR或者虚拟机指令)

  • 词法分析(tokenize):告诉计算机每个词是啥意思,分出很多token词
  • 语法分析:解析语句,然后生成中间代码或者抽象语法树

后端: 中间代码->目标代码

  • optimizer(优化器 最难最核心) :中间代码->中间
  • codegenerate:中间->最终目标代码(如果是物理架构 就会翻译为对应的X86架构等 虚拟机的话就会翻译为字节码 ) LLVM就是根据目标平台将IR字节码翻译到最终的目标代码

设计

  • 前后端合一,无中间优化,直接生成中间代码来通过解释器(自定义VM)执行
  • 边词法分析边语法分析同时生成字节码 (变量声明必须在开头)

计算基于register和stack顶

  • register:pc/sp/bp/ax
  • memory: code/data(静态变量/字面量)/stack
  • 指令集:save&load(mem<->register)/ 计算指令(算术 位 逻辑)/跳转(无条件 有条件 比较)/native call(IO: printf orw / 动态内存分配: malloc/free/memset )

PC 程序计数器,它存放的是一个内存地址,该地址中存放着 下一条 要执行的计算机指令。
SP 指针寄存器,永远指向当前的栈顶。注意的是由于栈是位于高地址并向低地址增长的,所以入栈时 SP 的值减小。
BP 基址指针。也是用于指向栈的某些位置,在调用函数时会使用到它。
AX 通用寄存器,我们的虚拟机中,它用于存放一条指令执行后的结果。

vm指令集

  • save/load: imm / lea / lc / li /sc / si /push
  • 运算:add /sub / mul /div /mod /or /xor /and / shc /shr
  • 分支跳转: jmp/jz / jnz /call /nvar /darg / re
  • native call: orw / close / printf / malloc /free / memset /exit /memcmp
 while (1) {
        cycle++; op = *pc++; // read instruction
        // load & save
        if (op == IMM)          ax = *pc++;                     // load immediate(or global addr)
        else if (op == LEA)     ax = (int)(bp + *pc++);         // load local addr
        else if (op == LC)      ax = *(char*)ax;                // load char
        else if (op == LI)      ax = *(int*)ax;                 // load int
        else if (op == SC)      *(char*)*sp++ = ax;             // save char to stack
        else if (op == SI)      *(int*)*sp++ = ax;              // save int to stack
        else if (op == PUSH)    *--sp = ax;                     // push ax to stack
        // jump
        else if (op == JMP)     pc = (int*)*pc;                 // jump
        else if (op == JZ)      pc = ax ? pc + 1 : (int*)*pc;   // jump if ax == 0
        else if (op == JNZ)     pc = ax ? (int*)*pc : pc + 1;   // jump if ax != 0
        // arithmetic
        else if (op == OR)      ax = *sp++ |  ax;
        else if (op == XOR)     ax = *sp++ ^  ax;
        else if (op == AND)     ax = *sp++ &  ax;
        else if (op == EQ)      ax = *sp++ == ax;
        else if (op == NE)      ax = *sp++ != ax;
        else if (op == LT)      ax = *sp++ <  ax;
        else if (op == LE)      ax = *sp++ <= ax;
        else if (op == GT)      ax = *sp++ >  ax;
        else if (op == GE)      ax = *sp++ >= ax;
        else if (op == SHL)     ax = *sp++ << ax;
        else if (op == SHR)     ax = *sp++ >> ax;
        else if (op == ADD)     ax = *sp++ +  ax;
        else if (op == SUB)     ax = *sp++ -  ax;
        else if (op == MUL)     ax = *sp++ *  ax;
        else if (op == DIV)     ax = *sp++ /  ax;
        else if (op == MOD)     ax = *sp++ %  ax;
        // some complicate instructions for function call
        // call function: push pc + 1 to stack & pc jump to func addr(pc point to)
        else if (op == CALL)    {*--sp = (int)(pc+1); pc = (int*)*pc;}
        // new stack frame for vars: save bp, bp -> caller stack, stack add frame
        else if (op == NVAR)    {*--sp = (int)bp; bp = sp; sp = sp - *pc++;}
        // delete stack frame for args: same as x86 : add esp, <size>
        else if (op == DARG)    sp = sp + *pc++;
        // return caller: retore stack, retore old bp, pc point to caller code addr(store by CALL) 
        else if (op == RET)     {sp = bp; bp = (int*)*sp++; pc = (int*)*sp++;}        
        // end for call function.
        // native call
        else if (op == OPEN)    {ax = open((char*)sp[1], sp[0]);}
        else if (op == CLOS)    {ax = close(*sp);}
        else if (op == READ)    {ax = read(sp[2], (char*)sp[1], *sp);}
        else if (op == PRTF)    {tmp = sp + pc[1] - 1; ax = printf((char*)tmp[0], tmp[-1], tmp[-2], tmp[-3], tmp[-4], tmp[-5]);}
        else if (op == MALC)    {ax = (int)malloc(*sp);}
        else if (op == FREE)    {free((void*)*sp);}
        else if (op == MSET)    {ax = (int)memset((char*)sp[2], sp[1], *sp);}
        else if (op == MCMP)    {ax = memcmp((char*)sp[2], (char*)sp[1], *sp);}
        else if (op == EXIT)    {printf("exit(%lld)\n", *sp); return *sp;}
        else {printf("unkown instruction: %lld, cycle: %lld\n", op, cycle); return -1;}
    }

分配空间

首先读文件到分配的空间里

然后分配代码段 数据段 栈段 符号表

int init_vm() {
    // allocate memory for virtual machine
    if (!(code = code_dump = malloc(MAX_SIZE))) {
        printf("could not malloc(%lld) for code segment\n", MAX_SIZE);
        return -1;
    }
    if (!(data = malloc(MAX_SIZE))) {
        printf("could not malloc(%lld) for data segment\n", MAX_SIZE);
        return -1;
    }
    if (!(stack = malloc(MAX_SIZE))) {
        printf("could not malloc(%lld) for stack segment\n", MAX_SIZE);
        return -1;
    }
    if (!(symbol_table = malloc(MAX_SIZE / 16))) {
        printf("could not malloc(%lld) for symbol_table\n", MAX_SIZE / 16);
        return -1;
    }
    memset(code, 0, MAX_SIZE);
    memset(data, 0, MAX_SIZE);
    memset(stack, 0, MAX_SIZE);
    memset(symbol_table, 0, MAX_SIZE / 16);
    return 0;
}

词法分析

词法分析函数就是根据扫描到的字符来确定类型

symbol_ptr是符号表指针

  • 数字或者字符组成就会放到符号表里,这里会遍历符号表,查看是否有相同的符号,有就直接返回这个符号,没有就会导入,返回token是ID类型
  // add new symbol
            symbol_ptr[Name] = (int)ch_ptr;  // 字符串的地址
            symbol_ptr[Hash] = token;     // 这里的token是之前计算的哈希值
            token = symbol_ptr[Token] = Id;  // 这里ID是一个枚举值,代表该符号的token是个ID(标识符)
  • 纯数字会根据进制解析,返回token是数字类型
  • 字符和字符串, 这里char是当作num类型的,另外字符串中可能包含转义字符,这里也要处理
  // handle string & char
        else if (token == '"' || token == '\'') {  // 单引号和双引号
            ch_ptr = data;
            while (*src != 0 && *src != token) {   // 解析知道遇到单引号或者双引号
                if ((token_val = *src++) == '\\') {  //遇到斜杠
                    // only support escape char '\n'    
                    if ((token_val = *src++) == 'n') token_val = '\n';   // 当转义字符
                }
                // store string to data segment
                if (token == '"') *data++ = token_val;  // 如果是字符串 将字符存进data段里
            }
            src++;
            if (token == '"') token_val = (int)ch_ptr;   // 结束字符是双引号 token_val是字符起始地址
            // single char is Num
            else token = Num;  // char 当Num返回
            return;
        }
  • 注释符和除法
 else if (token == '/') {  // 是除号
            if (*src == '/') {    // 当前字符也是除号
                // skip comments
                while (*src != 0 && *src != '\n') src++;  // 认为是注释跳过注释内容
            } else {     // 一个除
                // divide
                token = Div;  // token返回除号类型
                return;
            }
        }
  • 运算符号,除了部分符号是直接返回的,大部分符号都是需要返回对应的操作类型
 // handle all kinds of operators, copy from c4.
  else if (token == '=') {if (*src == '=') {src++; token = Eq;} else token = Assign; return;}
        else if (token == '+') {if (*src == '+') {src++; token = Inc;} else token = Add; return;}
        else if (token == '-') {if (*src == '-') {src++; token = Dec;} else token = Sub; return;}
        else if (token == '!') {if (*src == '=') {src++; token = Ne;} return;}
        else if (token == '<') {if (*src == '=') {src++; token = Le;} else if (*src == '<') {src++; token = Shl;} else token = Lt; return;}
        else if (token == '>') {if (*src == '=') {src++; token = Ge;} else if (*src == '>') {src++; token = Shr;} else token = Gt; return;}
        else if (token == '|') {if (*src == '|') {src++; token = Lor;} else token = Or; return;}
        else if (token == '&') {if (*src == '&') {src++; token = Land;} else token = And; return;}
        else if (token == '^') {token = Xor; return;}
        else if (token == '%') {token = Mod; return;}
        else if (token == '*') {token = Mul; return;}
        else if (token == '[') {token = Brak; return;}
        else if (token == '?') {token = Cond; return;}
        else if (token == '~' || token == ';' || token == '{' || token == '}' || token == '(' || token == ')' || token == ']' || token == ',' || token == ':') return;
   
void tokenize() {
    char* ch_ptr;
    while((token = *src++)) {
        if (token == '\n') line++;
        // skip marco
        else if (token == '#') while (*src != 0 && *src != '\n') src++;
        // handle symbol
        else if ((token >= 'a' && token <= 'z') || (token >= 'A' && token <= 'Z') || (token == '_')) {
            ch_ptr = src - 1;
            while ((*src >= 'a' && *src <= 'z') || (*src >= 'A' && *src <= 'Z')
                    || (*src >= '0' && *src <= '9') || (*src == '_'))
                // use token store hash value
                token = token * 147 + *src++;
            // keep hash
            token = (token << 6) + (src - ch_ptr);
            symbol_ptr = symbol_table;
            // search same symbol in table
            while(symbol_ptr[Token]) {
                if (token == symbol_ptr[Hash] && !memcmp((char*)symbol_ptr[Name], ch_ptr, src - ch_ptr)) {
                    token = symbol_ptr[Token];
                    return;
                }
                symbol_ptr = symbol_ptr + SymSize;
            }
            // add new symbol
            symbol_ptr[Name] = (int)ch_ptr;
            symbol_ptr[Hash] = token;
            token = symbol_ptr[Token] = Id;
            return;
        }
        // handle number
        else if (token >= '0' && token <= '9') {
            // DEC, ch_ptr with 1 - 9
            if ((token_val = token - '0'))
                while (*src >= '0' && *src <= '9') token_val = token_val * 10 + *src++ - '0';
            //HEX, ch_ptr with 0x
            else if (*src == 'x' || *src == 'X')
                while ((token = *++src) && ((token >= '0' && token <= '9') || (token >= 'a' && token <= 'f')
                        || (token >= 'A' && token <= 'F')))
                    // COOL!
                    token_val = token_val * 16 + (token & 0xF) + (token >= 'A' ? 9 : 0);
                // a?ascll?? 97 mod 16 =1 ? 0xf ? 1  + (token >= 'A' ? 9 : 0)
            // OCT, start with 0
            else while (*src >= '0' && *src <= '7') token_val = token_val * 8 + *src++ - '0';
            token = Num;
            return;
        }
        // handle string & char
        else if (token == '"' || token == '\'') {
            ch_ptr = data;
            while (*src != 0 && *src != token) {
                if ((token_val = *src++) == '\\') {
                    // only support escape char '\n'
                    if ((token_val = *src++) == 'n') token_val = '\n';
                }
                // store string to data segment
                if (token == '"') *data++ = token_val;
            }
            src++;
            if (token == '"') token_val = (int)ch_ptr; 
            // single char is Num
            else token = Num;
            return;
        }
        // handle comments or divide
        else if (token == '/') {
            if (*src == '/') {
                // skip comments
                while (*src != 0 && *src != '\n') src++;
            } else {
                // divide
                token = Div;
                return;
            }
        }
        // handle all kinds of operators, copy from c4.
        else if (token == '=') {if (*src == '=') {src++; token = Eq;} else token = Assign; return;}
        else if (token == '+') {if (*src == '+') {src++; token = Inc;} else token = Add; return;}
        else if (token == '-') {if (*src == '-') {src++; token = Dec;} else token = Sub; return;}
        else if (token == '!') {if (*src == '=') {src++; token = Ne;} return;}
        else if (token == '<') {if (*src == '=') {src++; token = Le;} else if (*src == '<') {src++; token = Shl;} else token = Lt; return;}
        else if (token == '>') {if (*src == '=') {src++; token = Ge;} else if (*src == '>') {src++; token = Shr;} else token = Gt; return;}
        else if (token == '|') {if (*src == '|') {src++; token = Lor;} else token = Or; return;}
        else if (token == '&') {if (*src == '&') {src++; token = Land;} else token = And; return;}
        else if (token == '^') {token = Xor; return;}
        else if (token == '%') {token = Mod; return;}
        else if (token == '*') {token = Mul; return;}
        else if (token == '[') {token = Brak; return;}
        else if (token == '?') {token = Cond; return;}
        else if (token == '~' || token == ';' || token == '{' || token == '}' || token == '(' || token == ')' || token == ']' || token == ',' || token == ':') return;
    }
}
  • 首先会讲一些关键字放进符号表里面,这些符号的Token会被设置为对应的关键字中枚举值对应的值,也就是哪个关键字类型
    OPEN开始的关键字放入符号表后还会设置Class(类型)和type(返回值)和value(对应的关键字枚举值),这里的Token为ID类型枚举值
void keyword() {
    int i;
    src = "char int enum if else return sizeof while "
        "open read close printf malloc free memset memcmp exit void main";
    // add keywords to symbol table
    i = Char; while (i <= While) {tokenize(); symbol_ptr[Token] = i++;}
    // add Native CALL to symbol table
    i = OPEN; while (i <= EXIT) {
        tokenize();
        symbol_ptr[Class] = Sys;
        symbol_ptr[Type] = INT;
        symbol_ptr[Value] = i++;
    }
    tokenize(); symbol_ptr[Token] = Char; // handle void type 
    tokenize(); main_ptr = symbol_ptr; // keep track of main
    src = src_dump;    //复制过来的地址
}
    else if (tmp_ptr[Class] == Num) {
            *++code = IMM; *++code = tmp_ptr[Value]; type = INT;
        }

语法分析

语法:词->句子
语义:就是表达的意思
文法:语法的合集


- program=var_define | fun_define
- var_define=enum_define | vari_define
   vari_define=type [*] Id [ ,] [*] ld ;
   enum_define= enum  ld  { ld=1, ld=2,……};
- fun_define = type ld  (param) { statment }
  1. param=  type [*] Id [ ,] type [*] ld ;
  2. statement= if_statment|while_statment|return_statement|empty_statement|normal_statement
  3. normal_statement= expression;
 

通过递归下降的方法逐层分解,直到表达式,表达式通过优先级爬上

这里语法分析和词法分析其实是一起的,先词法分析,然后再语法分析

  • 这里praser先分解析声明语句也就是enum_define | vari_define| fun_define这三种,通过先解析token来知道类型(Enum 还是Char 和Int)

  • Enum是Enum { } or Enum ID { }

assert是判断当前token是不是某个类型,然后解析下一个token

 if (token == Enum) {
            assert(Enum);
            if (token != '{') assert(Id); // skip enum name
            assert('{'); parse_enum(); assert('}');
        } 
 //这里是{}里面的值,一般是{ID=1,ID=2,ID……}类似这种形式
void parse_enum() {
    int i;
    i = 0; // enum index
    while (token != '}') {
        check_new_id();
        assert(Id);  //ID
        // handle custom enum index
        if (token == Assign) { assert(Assign); assert(Num); i = token_val;}  // =num
        symbol_ptr[Class] = Num; 
        symbol_ptr[Type] = INT;
        symbol_ptr[Value] = i++;  // num  后面的是要+1
        if (token == ',') tokenize();  //解析ID
    }
}
  • INT /Char ID ;或者 INT /Char ID ( ) { }
 else if (token == Int || token == Char) {    
            base_type = parse_base_type();  // 确保当前的token是基本类型,然后同时会解析下一个token
            // parse var or func definition
            while (token != ';' && token != '}') {
                // parse pointer's star
                type = base_type;
                while (token == Mul) {assert(Mul); type = type + PTR;}  // INT/Char *
                check_new_id();  // ID是新的,不能一个变量名声明两次
                assert(Id);   
                symbol_ptr[Type] = type; 
                if (token == '(') {   //函数
                    // function
                    symbol_ptr[Class] = Fun;
                    symbol_ptr[Value] = (int)(code + 1); //设置等会要转换的字节码要存储的函数起始地址
                    assert('('); parse_param(); assert(')'); assert('{');
                    parse_fun();
                } else {    //变量
                    // variable
                    symbol_ptr[Class] = Glo;
                    symbol_ptr[Value] = (int)data;  //当前变量的值在data上的位置,还没初始化
                    data = data + 8; // keep 64 bits for each var
                }
                // handle int a,b,c;
                if (token == ',') assert(',');     
            }
        }  
        
int ibp;
void parse_param() {
    int type, i;
    i = 0;
    while (token != ')') {
        type = parse_base_type();   // 解析type
        // parse pointer's star
        while (token == Mul) {assert(Mul); type = type + PTR;}  // 多重指针
        check_local_id();  //当前解析的token是ID并且它的type不是局部的
        assert(Id); 
        hide_global();  //设置重名的Class  Type  Value到全局去
        symbol_ptr[Class] = Loc;
        symbol_ptr[Type] = type;
        symbol_ptr[Value] = i++; //第几个参数
        if (token == ',') assert(',');
    }
    ibp = ++i;
}

void hide_global() {
    symbol_ptr[GClass] = symbol_ptr[Class];
    symbol_ptr[GType] = symbol_ptr[Type];
    symbol_ptr[GValue] = symbol_ptr[Value];
}


  • 解析函数体。先解析变量声明,然后有开辟栈帧的语句,然后解析语句,最后加个ret,然后将是loc的符号表恢复 (因为解析函数)

void parse_fun() {
    int type, i;
    i = ibp; // bp handle by NVAR itself.
    // local variables must be declare in advance 
    while (token == Char || token == Int) {
        type = parse_base_type();
        while (token != ';') {
            // parse pointer's star
            while (token == Mul) {assert(Mul); type = type + PTR;}
            check_local_id(); assert(Id);
            hide_global();
            symbol_ptr[Class] = Loc;
            symbol_ptr[Type] = type;
            symbol_ptr[Value] = ++i;
            if (token == ',') assert(',');
        }
        assert(';');
    }
    // new stack frame for vars
    *++code = NVAR;
    // stack frame size
    *++code = i - ibp;  
    while (token != '}') parse_stmt();  //解析所有的语句
    if (*code != RET) *++code = RET; // void function  void没有ret,此时设置个ret
    // recover global variables
    symbol_ptr = symbol_table;
    while (symbol_ptr[Token]) 
    {
        if (symbol_ptr[Class] == Loc) recover_global();
        symbol_ptr = symbol_ptr + SymSize;
    }
}


void recover_global() {
    symbol_ptr[Class] = symbol_ptr[GClass];
    symbol_ptr[Type] = symbol_ptr[GType];
    symbol_ptr[Value] = symbol_ptr[GValue];
}
  • 解析语句,有if while return {} ;赋值表达式语句,然后转换为对应的字节码
  • if 语句是()里面的条件表达式满足为0则跳转到else部分, 否则就执行为true的部分

void parse_stmt() {
    int* a;
    int* b;
    if (token == If) {    //if 语句
        assert(If); assert('('); parse_expr(Assign); assert(')');
        *++code = JZ; b = ++code; // JZ to false
        parse_stmt(); // parse true stmt
        if (token == Else) {
            assert(Else);
            *b = (int)(code + 3); // write back false point
            *++code = JMP; b = ++code; // JMP to endif
            parse_stmt(); // parse false stmt
        }
        *b = (int)(code + 1); // write back endif point
    }
    else if (token == While) {  // while 语句
        assert(While);
        a = code + 1; // write loop point
        assert('('); parse_expr(Assign); assert(')');
        *++code = JZ; b = ++code; // JZ to endloop
        parse_stmt();
        *++code = JMP; *++code = (int)a; // JMP to loop point
        *b = (int)(code + 1); // write back endloop point
    }
    else if (token == Return) {   //return 语句
        assert(Return);
        if (token != ';') parse_expr(Assign);
        assert(';');
        *++code = RET;
    }
    else if (token == '{') {   // {  { } 语句
        assert('{');
        while (token != '}') parse_stmt(Assign);
        assert('}');
    } 
    else if (token == ';') assert(';');    // ; 空语句  
    else { 
            parse_expr(Assign); assert(';');   // 赋值语句当作表达式 而不是语句
         }
}

递归下降

手把手教你构建 C 语言编译器(4)- 递归下降

<expr> ::= <expr> + <term>
         | <expr> - <term>
         | <term>

<term> ::= <term> * <factor>
         | <term> / <factor>
         | <factor>

<factor> ::= ( <expr> )
           | Num

上面是解析表达式的递归下降的方法

表达式

  • statement : 做什么 if while return
  • expression:求值 2+3 4*5
赋值语句是有个返回值,看作表达式
if ((a=3!=0) {  }

一元

1. ++ --  a++优先级小于++a和其他三种
2. + -    正负号
3. ~4. & *
5. ()  最高

另外四个相等 谁靠近就是谁优先。在变量左边,谁更近

一元大于二元

二元

7. =8.|| &&
9.   | & ^
10. == > < >= <= << >>
11. + - 
12. * / %
13. [ ]

优先级爬山

就是分别压操作数和符号,但出现比当前栈顶的符号的优先级低的时候,就运算此时栈顶的符号和对应的操作数

3+3*4-5

在这里插入图片描述
此时压入符号栈的是-号,但优先级别小于*,所以此时会计算 3*4的结果再压入数据栈,然后再将-号压入符号栈

所谓优先级爬上就是压入的操作符需要不断往上走,遇到比当前山峰低的优先级符号就要一直计算相关符号,直到此时山顶符号优先级小于或者等于要压入的符号
优先级

*++a++=i == 0?2+3:4*5

这里先解析++a然后解析*++a,然后解析a++…………不具体演示了,按照对应的优先级来爬上就行

  • 解析表达式,precd代表当前解析的最大优先级,按照优先级顺序来解析,一元还是正常解析(出来a++),但二元只会解析比当前precd大的(a++算到仅仅小于()的优先级别了)

int type; // pass type in recursive parse expr
// type 是全局变量
// tmp_type是局部变量 ,每次递归都变新的
void parse_expr(int precd) {
    int tmp_type, i;
    int* tmp_ptr;
    // const number
    if (token == Num) {
        tokenize();
        *++code = IMM;
        *++code = token_val;
        type = INT;
    } 
    // const string
    else if (token == '"') {
        *++code = IMM;
        *++code = token_val; // string addr
        assert('"'); while (token == '"') assert('"'); // handle multi-row
        data = (char*)((int)data + 8 & -8); // add \0 for string & align 8
        type = PTR;
    }
    else if (token == Sizeof) {
        tokenize(); assert('(');
        type = parse_base_type();
        while (token == Mul) {assert(Mul); type = type + PTR;}
        assert(')');
        *++code = IMM;
        *++code = (type == CHAR) ? 1 : 8; 
        type = INT;
    }
    // handle identifer: variable or function all
    else if (token == Id) {
        tokenize();
        tmp_ptr = symbol_ptr; // for recursive parse
        // function call
        if (token == '(') {  // 函数调用
            assert('(');
            i = 0; // number of args
            while (token != ')') {
                parse_expr(Assign);  //参数值
                *++code = PUSH; i++;    // push进栈
                if (token == ',') assert(',');
            } assert(')');
            // native call
            if (tmp_ptr[Class] == Sys) *++code = tmp_ptr[Value]; //系统调用 直接就是对应函数得字节码
            // fun call
            else if (tmp_ptr[Class] == Fun) {*++code = CALL; *++code = tmp_ptr[Value];}
            // 否则就是call 函数地址
            else {printf("line %lld: invalid function call\n", line); exit(-1);}
            // delete stack frame for args
            if (i > 0) {*++code = DARG; *++code = i;} // 最后释放会参数空间
            type = tmp_ptr[Type];
        }
        // handle enum value
        else if (tmp_ptr[Class] == Num) {   //枚举值 往AX存变量值 当字面量处理
            *++code = IMM; *++code = tmp_ptr[Value]; type = INT;
        }
        // handle variables
        else {
            // local var, calculate addr base ibp
            //ibp - tmp_ptr[Value]是参数的偏移
            if (tmp_ptr[Class] == Loc) {*++code = LEA; *++code = ibp - tmp_ptr[Value];}
            //  局部变量在栈上,lea将其地址存在AX上  ibp是参数个数  tmp_ptr[Value]是第几个局部变量
            // global var
            else if (tmp_ptr[Class] == Glo) {*++code = IMM; *++code = tmp_ptr[Value];}
            // 全局变量tmp_ptr[Value]存储着全局变量地址
            else {printf("line %lld: invalid variable\n", line); exit(-1);}
            type = tmp_ptr[Type];
            *++code = (type == CHAR) ? LC : LI;
            // 根据地址和对应的变量类型设置对应的加载方式到ax
            // 如果是指针型就不设置
        }
    }
    // cast or parenthesis  类型转换  最后设置type类型   其他就直接生成将值保存在ax的字节码
    else if (token == '(') {
        assert('(');
        if (token == Char || token == Int) {
            tokenize();
            tmp_type = token - Char + CHAR;
            while (token == Mul) {assert(Mul); tmp_type = tmp_type + PTR;}
            // use precedence Inc represent all unary operators
            assert(')'); parse_expr(Inc); type = tmp_type;  // 解析完变量后将type设置为之前()里面的
        } else {
            parse_expr(Assign); assert(')');
        }
    }
    // derefer
    else if (token == Mul) {  //parse_expr(Inc);会把变量地址保持到ax里,然后LC。LI
        tokenize(); parse_expr(Inc);
        if (type >= PTR) type = type - PTR;
        else {printf("line %lld: invalid dereference\n", line); exit(-1);}
        *++code = (type == CHAR) ? LC : LI;
    }
    // reference
    else if (token == And) {  //  &符号代表 And 与的意思,作为二元运算符时候,这里代表取地址,一个&就代表And了
        tokenize(); parse_expr(Inc);
        if (*code == LC || *code == LI) code--; // rollback load by addr
        // parse_expr(Inc);里面解析到符号会IMM 地址 然后LC/LI,但如果前面存在&,就不需要LC/LI
        else {printf("line %lld: invalid reference\n", line); exit(-1);}
        type = type + PTR;
    }
    // Not
    else if (token == '!') {
        tokenize(); parse_expr(Inc);  //生成++a然后存到ax寄存器的字节码
        *++code = PUSH; *++code = IMM; *++code = 0; *++code = EQ;  //判断和0相等 符合!逻辑
        type = INT;
    }
    // bitwise
    else if (token == '~') {
        tokenize(); parse_expr(Inc); //生成++a然后存到ax寄存器的字节码
        *++code = PUSH; *++code = IMM; *++code = -1; *++code = XOR; //和-1异或,相等与位反转
        type = INT;
    }
    // positive
    else if (token == And) {tokenize(); parse_expr(Inc); type = INT;}// +号,作为正数,没啥用
    // negative
    else if (token == Sub) {
        tokenize(); parse_expr(Inc);
        *++code = PUSH; *++code = IMM; *++code = -1; *++code = MUL;
        type = INT;  //乘-1
    }
    // ++var --var
    else if (token == Inc || token == Dec) {
        i = token; tokenize(); parse_expr(Inc); //变量地址保存在ax  此时如果为++a++ 解析a++时候解析完a就会跳转到二元运算符优先级别处理,此时对应a++
        // save var addr, then load var val
        if (*code == LC) {*code = PUSH; *++code = LC;}  //压入变量地址,取变量值到ax
        else if (*code == LI) {*code = PUSH; *++code = LI;}
        else {printf("line %lld: invalid Inc or Dec\n", line); exit(-1);}
        *++code = PUSH; // save var val  保存当前变量值
        *++code = IMM; *++code = (type > PTR) ? 8 : 1; //+指针还是普通加
        *++code = (i == Inc) ? ADD : SUB; // 当前栈顶变量值 相加或者相减 (1 或者8)
        *++code = (type == CHAR) ? SC : SI; // write back to var addr 变量地址在栈顶,写回变量
    }
    else {printf("line %lld: invalid expression\n", line); exit(-1);}
    // use [precedence climbing] method to handle binary(or postfix) operators
    while (token >= precd) {  //二元运算符和a++
        tmp_type = type;    
        // assignment
        if (token == Assign) {
            tokenize();
            if (*code == LC || *code == LI) *code = PUSH;
            //此时ax存的是第一个参数的地址
            else {printf("line %lld: invalid assignment\n", line); exit(-1);}
            parse_expr(Assign); type = tmp_type; // type can be cast
            // 解析完变量后将type设置为被赋值的ID的类型

            // 第二个参数值被放到ax
            *++code = (type == CHAR) ? SC : SI;
            // 往第一个参数的地址处存第二个参数值  所谓类型就是加载方式而已,指针不加载,其他就需要加载
        }
        // ? :, same as if stmt
        else if (token == Cond) {
            tokenize(); *++code = JZ; tmp_ptr = ++code;
            parse_expr(Assign); assert(':');
            *tmp_ptr = (int)(code + 3);
            //为零跳转到 :后面的
            *++code = JMP; tmp_ptr = ++code; // save endif addr
            parse_expr(Cond);
            *tmp_ptr = (int)(code + 1); // write back endif point
            //第一个执行完直接结束
        }
        // logic operators, simple and boring, copy from c4
        // ||
        else if (token == Lor) {
            tokenize(); *++code = JNZ; tmp_ptr = ++code;
            parse_expr(Land); *tmp_ptr = (int)(code + 1); type = INT;}
        // 第一个参数不为零就跳转到外面取,此时ax依然不为零
        // &&
        else if (token == Land) {
            tokenize(); *++code = JZ; tmp_ptr = ++code;
            parse_expr(Or); *tmp_ptr = (int)(code + 1); type = INT;}
        // 为零就直接跳转进入 此时ax依然为零,不为零就会解析下一个,ax保存下一个的值

        // 下面的push都是将第一个参数压入栈
        else if (token == Or)  {tokenize(); *++code = PUSH; parse_expr(Xor); *++code = OR;  type = INT;}
        else if (token == Xor) {tokenize(); *++code = PUSH; parse_expr(And); *++code = XOR; type = INT;}
        else if (token == And) {tokenize(); *++code = PUSH; parse_expr(Eq);  *++code = AND; type = INT;}
        else if (token == Eq)  {tokenize(); *++code = PUSH; parse_expr(Lt);  *++code = EQ;  type = INT;}
        else if (token == Ne)  {tokenize(); *++code = PUSH; parse_expr(Lt);  *++code = NE;  type = INT;}
        else if (token == Lt)  {tokenize(); *++code = PUSH; parse_expr(Shl); *++code = LT;  type = INT;}
        else if (token == Gt)  {tokenize(); *++code = PUSH; parse_expr(Shl); *++code = GT;  type = INT;}
        else if (token == Le)  {tokenize(); *++code = PUSH; parse_expr(Shl); *++code = LE;  type = INT;}
        else if (token == Ge)  {tokenize(); *++code = PUSH; parse_expr(Shl); *++code = GE;  type = INT;}
        else if (token == Shl) {tokenize(); *++code = PUSH; parse_expr(Add); *++code = SHL; type = INT;}
        else if (token == Shr) {tokenize(); *++code = PUSH; parse_expr(Add); *++code = SHR; type = INT;}
        // arithmetic operators
        else if (token == Add) {
            tokenize(); *++code = PUSH; parse_expr(Mul);
            // int pointer * 8
            if (tmp_type > PTR) {*++code = PUSH; *++code = IMM; *++code = 8; *++code = MUL;}
            // +的第二个参数 *8
            *++code = ADD; type = tmp_type;
            // 再和第一个参数相加
        }
        else if (token == Sub) {// 此时已经解析到-号了,那么之前的已经在ax里面
            tokenize(); *++code = PUSH; parse_expr(Mul);
            //parse_expr会执行优先级大于等于precd的操作来作为返回结果存在到AX里
            //  push 会将之前的ax,也就是第一个参数存到栈里
            if (tmp_type > PTR && tmp_type == type) {
                // pointer - pointer, ret / 8
                *++code = SUB; *++code = PUSH;
                // 先将结果push到栈里
                *++code = IMM; *++code = 8;
                // ax设置为8
                *++code = DIV; type = INT;}
               // 除8
            else if (tmp_type > PTR) {  // 指针减一个整数  减去的数要乘8
                *++code = PUSH;
                *++code = IMM; *++code = 8;
                *++code = MUL;
                // ax现在为8*第二个参数  栈也是原来压入的第一个参数
                *++code = SUB; type = tmp_type;}
            else *++code = SUB;   // 都是整数,正常相减
        }
        else if (token == Mul) {tokenize(); *++code = PUSH; parse_expr(Inc); *++code = MUL; type = INT;}
        else if (token == Div) {tokenize(); *++code = PUSH; parse_expr(Inc); *++code = DIV; type = INT;}
        else if (token == Mod) {tokenize(); *++code = PUSH; parse_expr(Inc); *++code = MOD; type = INT;}
        // var++, var--
        else if (token == Inc || token == Dec) {
            if (*code == LC) {*code = PUSH; *++code = LC;} // save var addr上
            else if (*code == LI) {*code = PUSH; *++code = LI;}
            // // 原来只有个一个加号是LC,现在两个加号变为先保留地址在栈上,再取值到ax
            else {printf("%lld: invlid operator=%lld\n", line, token); exit(-1);}
            *++code = PUSH; *++code = IMM; *++code = (type > PTR) ? 8 : 1;
            //将值压栈 根根据是否是指针设置ax单位
            *++code = (token == Inc) ? ADD : SUB;
            //加后的值保存在ax里
            *++code = (type == CHAR) ? SC : SI; // save value ++ or -- to addr
            // 此时栈顶为第一个参数地址 SC再存回去
            *++code = PUSH; *++code = IMM; *++code = (type > PTR) ? 8 : 1;
            //再将值压栈  同样设置单位
            *++code = (token == Inc) ? SUB : ADD; // restore value before ++ or --
            // 将ax复原之前的
            tokenize();
        }
        // a[x] = *(a + x)
        else if (token == Brak) {//此时栈顶是a变量的地址
            assert(Brak); *++code = PUSH; parse_expr(Assign); assert(']');
            //解析括号[]内的值放到ax里
            if (tmp_type > PTR) {*++code = PUSH; *++code = IMM; *++code = 8; *++code = MUL;}
            // 指针类型数组就乘偏移*8
            else if (tmp_type < PTR) {printf("line %lld: invalid index op\n", line); exit(-1);}
            *++code = ADD; type = tmp_type - PTR;
            //数组起始地址和偏移量相加放到ax里
            *++code = (type == CHAR) ? LC : LI;
            // 得到元素值
        }
        else {printf("%lld: invlid token=%lld\n", line, token); exit(-1);}
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908108.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于我、重生到500年前凭借C语言改变世界科技vlog.14——常见C语言算法

文章目录 1.冒泡排序2.二分查找3.转移表希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 根据当前所学C语言知识&#xff0c;对前面知识进行及时的总结巩固&#xff0c;出了这么一篇 vlog 介绍当前所学知识能遇到的常见算法&#xff0c;这些算法是…

我也谈AI

“随着人工智能技术的不断发展&#xff0c;我们已经看到了它在各行业带来的巨大变革。在医疗行业中&#xff0c;人工智能技术正在被应用于病例诊断、药物研发等方面&#xff0c;为医学研究和临床治疗提供了新的思路和方法&#xff1b;在企业中&#xff0c;人工智能技术可以通过…

Flutter 13 网络层框架架构设计,支持dio等框架。

在移动APP开发过程中&#xff0c;进行数据交互时&#xff0c;大多数情况下必须通过网络请求来实现。客户端与服务端常用的数据交互是通过HTTP请求完成。面对繁琐业务网络层&#xff0c;我们该如何通过网络层架构设计来有效解决这些问题&#xff0c;这便是网络层框架架构设计的初…

Spring Boot2.x教程:(十)从Field injection is not recommended谈谈依赖注入

从Field injection is not recommended谈谈依赖注入 1、问题引入2、依赖注入的三种方式2.1、字段注入&#xff08;Field Injection&#xff09;2.2、构造器注入&#xff08;Constructor Injection&#xff09;2.3、setter注入&#xff08;Setter Injection&#xff09; 3、为什…

Nginx的基础架构解析(下)

1. Nginx模块 1.1 Nginx中的模块化设计 Nginx 的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单&#xff0c;便于开发&#xff0c;同时也便于对系统进行功能扩展。Nginx 将各功能模块组织成一条链&#xff0c;当有请求到达的时候&…

【网络】网络层协议IP

目录 IP协议报头 报头分离和向上交付 四位版本 8位服务类型 16位总长度 八位生存时间 16位标识一行 网段划分 DHCP 私有IP范围 公网划分之CIDR 特殊的IP地址 缓解IP地址不够用的方法 NAT技术 路由 IP是用来主机定位和路由选择的&#xff0c;它提供了一种能力&am…

HTML 基础标签——多媒体标签<img>、<object> 与 <embed>

文章目录 1. `<img>` 标签主要属性示例注意事项2. `<object>` 标签概述主要属性示例注意事项3. `<embed>` 标签概述主要属性示例注意事项小结在现代网页设计中,多媒体内容的使用变得越来越重要,因为它能够有效增强用户体验、吸引注意力并传达信息。HTML 提…

【Canal 中间件】Canal 实现 MySQL 增量数据的异步缓存更新

文章目录 一、安装 MySQL1.1 启动 mysql 服务器1.2 开启 Binlog 写入功能1.2.1创建 binlog 配置文件1.2.2 修改配置文件权限1.2.3 挂载配置文件1.2.4 检测 binlog 配置是否成功 1.3 创建账户并授权 二、安装 RocketMQ2.1 创建容器共享网络2.2 启动 NameServer2.3 启动 Broker2.…

深度学习(九):推荐系统的新引擎(9/10)

一、深度学习与推荐系统的融合 深度学习在推荐系统中的融合并非偶然。随着互联网的飞速发展&#xff0c;数据量呈爆炸式增长&#xff0c;传统推荐系统面临着诸多挑战。例如&#xff0c;在处理大规模、高维度的数据时&#xff0c;传统方法往往显得力不从心。而深度学习以其强大的…

masm汇编字符串输出演示

assume cs:code, ds:datadata segmentmassage db zhouzunjie, 0dh, 0ah, $ data endscode segmentstart:mov ax, datamov ds, axmov ah, 09hlea dx, massageint 21hmov ax, 4c00hint 21hcode ends end start 效果演示&#xff1a;

在昇腾Ascend 910B上运行Qwen2.5推理

目前在国产 AI 芯片&#xff0c;例如昇腾 NPU 上运行大模型是一项广泛且迫切的需求&#xff0c;然而当前的生态还远未成熟。从底层芯片的算力性能、计算架构的算子优化&#xff0c;到上层推理框架对各种模型的支持及推理加速&#xff0c;仍有很多需要完善的地方。 今天带来一篇…

HarmonyOS一次开发多端部署三巨头之界面级一多开发

界面级一多开发 引言1. 布局能力1.1 自适应布局1.1.1 拉伸能力1.1.2 均分能力1.1.3 占比能力1.1.4 缩放能力1.1.5延伸能力1.1.6 隐藏能力1.1.7 折行能力 1.2 响应式布局1.2.1 断点和媒体查询1.2.2 栅格布局 2. 视觉风格2.1 分层参数2.2 自定义资源 3. 交互归一4. IDE多设备预览…

(58)LMS自适应滤波算法与系统辨识的MATLAB仿真

文章目录 前言一、LMS算法的基本步骤二、LMS算法的一些主要应用1. 通信系统2. 信号分离与增强3. 控制系统4. 生物医学信号处理5. 机器学习与模式识别6. 其他应用 三、LMS算法用于系统辨识的MATLAB仿真四、仿真结果 前言 LMS&#xff08;Least Mean Squares&#xff0c;最小均方…

bootstrap应用1——计算n从1-100000的每个整数,第j个观测在自助法样本里的概率。

计算n从1-100000的每个整数&#xff0c;第j个观测在自助法样本里的概率。 pr function(n) return(1 - (1 - 1/n)^n) x 1:10000 plot(x, pr(x))

AI-基本概念-向量、矩阵、张量

1 需求 需求&#xff1a;Tensor、NumPy 区别 需求&#xff1a;向量、矩阵、张量 区别 2 接口 3 示例 4 参考资料 【PyTorch】PyTorch基础知识——张量_pytorch张量-CSDN博客

【设计模式】策略模式定义及其实现代码示例

文章目录 一、策略模式1.1 策略模式的定义1.2 策略模式的参与者1.3 策略模式的优点1.4 策略模式的缺点1.5 策略模式的使用场景 二、策略模式简单实现2.1 案例描述2.2 实现代码 三、策略模式的代码优化3.1 优化思路3.2 抽象策略接口3.3 上下文3.4 具体策略实现类3.5 测试 参考资…

2025年PMP考试的3A好考吗?

确实&#xff0c;PMP正式抛弃第六版用第七版教材了&#xff0c;但是考纲还是跟24年一样的&#xff0c;情景题多&#xff0c;考的比之前灵活&#xff0c;但是 3A 的人也不少&#xff0c;按照机构的计划来学习并没有很难&#xff0c;给大家说说我的备考经历吧&#xff0c;希望对你…

VScode + PlatformIO 了解

​Visual Studio Code Visual Studio Code&#xff08;简称 VS Code&#xff09;是一款由微软开发且跨平台的免费源代码编辑器。该软件以扩展的方式支持语法高亮、代码自动补全&#xff08;又称 IntelliSense&#xff09;、代码重构功能&#xff0c;并且内置了工具和 Git 版本…

完美日记营销模式对开源 AI 智能名片 2 + 1 链动模式 S2B2C 商城小程序的启示

摘要&#xff1a;本文通过分析完美日记在营销中利用社会基础设施升级红利、网红与新流量平台、KOL 和私域流量等策略取得成功的案例&#xff0c;探讨其对开源 AI 智能名片 2 1 链动模式 S2B2C 商城小程序在营销推广、用户获取与留存、提升复购率等方面的启示&#xff0c;为商城…

Failed to install Visual Studio Code update

当关闭vsCode的时候&#xff0c;出现了下面的报错&#xff1a; 可能是之前将vscode文件换了位置导致的&#xff0c;并且vscode在桌面的图标也变成了下面这个&#xff1a; 解决方法&#xff1a; 找到上图路径的log文件并打开&#xff1a; 搜索电脑中的Code.exe文件 并粘贴到上…