Tuesday, March 17, 2020

針對虛擬機一次的分析


可能是我的問題,或者是編譯器的問題 , 或者組譯器問題 或者 虛擬機問題
在目前來說我發現在 cpu0 這個程式,我的input 是
sum = 0;

for (i=0; i<=10; i++)
{
  sum = sum + i;
}
return sum;
我拿去run 裡面的虛擬機 cpu.h? 好像會無窮迴圈我把暫存器印出來 發現裡面呼叫 define 裡面的值並沒有完成設值

也就是根本沒有 對暫存器做存入的動作
我嘗試修改了這個方法,可能是編譯的環境不一樣?

LoadInt32 test


void LoadInt32void(int *p , BYTE  *m)
{
   BYTE tmp[10];
   strcpy(tmp,m);
   *p= (INT32) (tmp[0]<<24|tmp[1]<<16|tmp[2]<<8|tmp[3]); 
   printf("ldi ra ex2:%d\n", (INT32) (tmp[0]<<24|tmp[1]<<16|tmp[2]<<8|tmp[3])); 
};


      case OP_LD : { //This is an empty staxxtement.
     // LoadInt32(i, m, addr) 
    BYTE *tmp=&m[caddr];
 //  R[ra]=(INT32)(m[0]<<24|m[1]<<16|m[2]<<8|m[3]); 
    printf("ld ra ex:%p\n",&R[ra])  ;
    printf("ld ra ex:%p\n", m[caddr])  ;
    printf("ld ra ex:%x\n", (m[0]<<24|m[1]<<16|m[2]<<8|m[3]))  ;
    printf("ld ra ex:%d\n", (INT32) (m[0]<<24|m[1]<<16|m[2]<<8|m[3]))  ;
    LoadInt32void(&R[ra],&m[caddr]);

經過了一段時間


這邊可以看到我們的暫存器,都一直處於空的狀態
我從指令及的asm 了解到 在 LD 和 LDI 在 LD 和 LDI 這邊怎麼吃不到 變數的值呢
首先我懷疑了 LoadInt32 和 StoreInt32 這兩個函數 是在 解析 從 變數中加載 數值
另一個 我以為 Define 出錯 ,最後一路追回我們的 Assembler
#include "Cpu0.h"

void runObjFile(char *objFile) {                 // 虛擬機器主函數
  printf("===VM0:run %s on CPU0===\n", objFile);               
  Cpu0 *cpu0 = Cpu0New(objFile);                 // 建立CPU0物件
  Cpu0Run(cpu0, 0);                              // 開始執行
  Cpu0Dump(cpu0);                                // 傾印暫存器
  Cpu0Free(cpu0);                                // 釋放記憶體 
}

Cpu0* Cpu0New(char *objFile) {                   // 建立 CPU0 物件                 
  Cpu0 *cpu0=ObjNew(Cpu0, 1);                    //  分配 CPU0 物件空間 
  cpu0->m = newFileBytes(objFile, &cpu0->mSize); //  載入映像檔 objFile 到記憶體 m 中
  return cpu0;                                                                
}                                                                             
                                                                              
void Cpu0Free(Cpu0 *cpu0) {                      // 刪除 CPU0 物件                 
  freeMemory(cpu0->m);                           //  釋放 CPU0 的 memory 
  ObjFree(cpu0);                                 //  釋放 CPU0 物件                                         
}                                                                                         
                                                                                                                                                            
#define bits(i, from, to) ((UINT32) i << (31-to) >> (31-to+from)) // 取得 from 到 to 之間的位元                   
#define ROR(i, k) (((UINT32)i>>k)|(bits(i,32-k, 31)<<(32-k)))// 向右旋轉k位元                                           
#define ROL(i, k) (((UINT32)i<<k)|(bits(i,0,k-1)<<(32-k)))   // 向左旋轉k位元                                             
#define SHR(i, k) ((UINT32)i>>k)                             // 向右移位k位元                
#define SHL(i, k) ((UINT32)i<<k)                             // 向左移位k位元                
#define bytesToInt32(p) (INT32)(p[0]<<24|p[1]<<16|p[2]<<8|p[3])// 4 byte轉 int
#define bytesToInt16(p) (INT16)(p[0]<<8|p[1])                // 2 byte轉 INT16               
#define int32ToBytes(i, bp) { bp[0]=i>>24; bp[1]=i>>16; bp[2]=i>>8; bp[3]=i;} // int轉為4 byte                                             
#define StoreInt32(i, m, addr) { BYTE *p=&m[addr]; int32ToBytes(i, p); } // i=m[addr…addr+3]                                         
#define LoadInt32(i, m, addr) { BYTE *p=&m[addr]; i=bytesToInt32(p) ; printf("dld ra ex:%x\n",&i)  ;  } // m[addr..addr+3]=i                                         
#define StoreByte(b, m, addr) { m[addr] = (BYTE) b; }        // m[addr]=b                                                 
#define LoadByte(b, m, addr) { b = m[addr]; }                // b=m[addr]                    
                                                                                          
#define PC R[15]                                             // PC is R[15]                  
#define LR R[14]                                             // LR is R[14]                  
#define SP R[13]                                             // SP is R[13]                  
#define SW R[12]                                             // SW is R[12]                  
void LoadInt32void(int *p , BYTE  *m)
{
   BYTE tmp[3];
   strcpy(tmp,m);
   *p= bytesToInt32(tmp); 
   printf("ldi ra ex2:%d\n",bytesToInt32(tmp)); 
  //   *p= (INT32) (tmp[0]<<24|tmp[1]<<16|tmp[2]<<8|tmp[3]); 
  //  printf("ldi ra ex2:%d\n", (INT32) (tmp[0]<<24|tmp[1]<<16|tmp[2]<<8|tmp[3])); 
};
void StoreInt32void(int *p , BYTE  *m)
{
   BYTE tmp[3];
   strcpy(tmp,m);
   int32ToBytes( *p,tmp); 
};


void Cpu0Run(Cpu0 *cpu0, int start) {                        // 虛擬機器的主要執行函數                   
  char buffer[200];
  unsigned int IR, op, ra, rb, rc, cc;                                                                         
  int c5, c12, c16, c24, caddr, raddr;                                                                
  unsigned int N, Z;
  BYTE *m=cpu0->m;                                                                                    
  int  *R=cpu0->R;                                           
  PC = start;                                                // 設定起始位址,準備開始執行                             
  LR = -1;                                                                                
  BOOL stop = FALSE;                                                                                  
  int testi=0;
  while (!stop) {                                            // 如果尚未結束    
    printf("time: %d\n",testi)  ;
    testi+=1;
    R[0] = 0;                                                // R[0] 永遠為 0                            
   // LoadInt32(IR, m, PC);                                    // 指令擷取,IR=[PC..PC+3]  
    LoadInt32void(&IR ,&m[PC]) ;                              
    cpu0->IR = IR;                                           // 取得指令暫存器                           
    PC += 4;                                                 // 擷取完將 PC 加 4,指向下一個指令
    op = bits(IR, 24, 31);                                   // 取得 op 欄位,IR[24..31]
    printf("op:%d\n",op)  ;
    ra = bits(IR, 20, 23);                                   // 取得 ra 欄位,IR[20..23]    
    rb = bits(IR, 16, 19);                                   // 取得 rb 欄位,IR[16..19]
    rc = bits(IR, 12, 15);                                   // 取得 rc 欄位,IR[12..15]
    c5 = bits(IR,  0, 4);                                    // 取得 5  位元的 cx 
    c12= bits(IR, 0, 11);                                    // 取得 12 位元的 cx 
    c16= bits(IR, 0, 15);                                    // 取得 16 位元的 cx                         
    c24= bits(IR, 0, 23);                                    // 取得 24 位元的 cx                                         
    N  = bits(SW, 31, 31);
    Z  = bits(SW, 30, 30);
    if (bits(IR, 11, 11)!=0) c12 |= 0xFFFFF000;              // 若 cx 為負數,則調整為2補數格式                                         
    if (bits(IR, 15, 15)!=0) c16 |= 0xFFFF0000;              // 若 cx 為負數,則調整為2補數格式                                        
    if (bits(IR, 23, 23)!=0) c24 |= 0xFF000000;              // 若 cx 為負數,則調整為2補數格式                                                                                 
    caddr = R[rb]+c16;                                       // 取得位址[Rb+cx]                          
    raddr = R[rb]+R[rc];                                     // 取得位址[Rb+Rc]                          
    switch (op) {                                            // 根據op執行動作                           
      case OP_LD : { //This is an empty staxxtement.
     // LoadInt32(i, m, addr) 
    //BYTE *tmp=&m[caddr];
 //  R[ra]=(INT32)(m[0]<<24|m[1]<<16|m[2]<<8|m[3]); 
    // printf("ld ra ex:%p\n",&R[ra])  ;
    // printf("ld ra ex:%p\n", m[caddr])  ;
    // printf("ld ra ex:%x\n", (m[0]<<24|m[1]<<16|m[2]<<8|m[3]))  ;
    // printf("ld ra ex:%d\n", (INT32) (m[0]<<24|m[1]<<16|m[2]<<8|m[3]))  ;
    LoadInt32void(&R[ra],&m[caddr]);
    //    LoadInt32(R[ra], m, caddr); 
  //  printf("ld ra ex:%x\n", R[ra])  ;
      // BYTE *p=&m[caddr];
      // BYTE *x=&m[caddr];
      // printf("轉換後address%s\n",x[0]);
      // R[ra]=(INT32)(p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3]);
      // printf("轉換後%x\n",(INT32)(p[0]<<24|p[1]<<16|p[2]<<8|p[3]));
      // printf("\nld ra tmp id:%d\n",ra)  ;
      // printf("ld ra :%x\n",(INT32)(x[0]<<24|x[1]<<16|x[2]<<8|x[3]))  ;
      // printf("ld ra :%x\n",caddr)  ;
      //  printf("ld ra ex:%x\n",R[ra])  ;

    }
      break;        // 處理 LD 指令    

      case OP_ST :// StoreInt32(R[ra], m, caddr); 
      {
         StoreInt32void(&R[ra],&m[caddr]);
      }break;       // 處理 ST 指令                            
      case OP_LDB: LoadByte(R[ra], m, caddr); break;         // 處理 LDB 指令                            
      case OP_STB: StoreByte(R[ra], m, caddr); break;        // 處理 STB 指令                            
      case OP_LDR:{ 
        //LoadInt32(R[ra], m, raddr); 
          LoadInt32void(&R[ra],&m[raddr]);
      }
      break;        // 處理 LDR 指令                           
      case OP_STR:{
         //StoreInt32(R[ra], m, raddr);
          StoreInt32void(&R[ra],&m[raddr]);
        
      } break;       // 處理 STR 指令                           
      case OP_LBR: LoadByte(R[ra], m, raddr); break;         // 處理 LBR 指令                           
      case OP_SBR: StoreByte(R[ra], m, raddr); break;        // 處理 SBR 指令                           
      case OP_LDI: R[ra] = c16;
          // byte b = c16;
           printf("c :%x\n",c16)  ;
          //           printf("ra id:%x\n",ra)  ;
          //           R[ra] = c16;
                     printf("ra ex:%x\n",R[ra])  ;
  
       break;                       // 處理 LDI 指令                           
      case OP_CMP: {                                         // 處理CMP指令,根據比較結果,設定 N,Z 旗標 
        if (R[ra] > R[rb]) {                                 // > : SW(N=0, Z=0)
          SW &= 0x3FFFFFFF;                                  // N=0, Z=0
        } else if (R[ra] < R[rb]) {                          // < : SW(N=1, Z=0, ....)                                                
          SW |= 0x80000000;                                  // N=1;
          SW &= 0xBFFFFFFF;                                  // Z=0;
        } else {                                             // = : SW(N=0, Z=1)                      
          SW &= 0x7FFFFFFF;                                  // N=0;
          SW |= 0x40000000;                                  // Z=1;
        }
        ra = 12;
        break;                                                                                        
      } case OP_MOV: R[ra] = R[rb]; break;                   // 處理MOV指令                             
      case OP_ADD: R[ra] = R[rb] + R[rc]; break;             // 處理ADD指令                             
      case OP_SUB: R[ra] = R[rb] - R[rc]; break;             // 處理SUB指令                             
      case OP_MUL: R[ra] = R[rb] * R[rc]; break;             // 處理MUL指令                             
      case OP_DIV: R[ra] = R[rb] / R[rc]; break;             // 處理DIV指令                             
      case OP_AND: R[ra] = R[rb] & R[rc]; break;             // 處理AND指令                             
      case OP_OR:  R[ra] = R[rb] | R[rc]; break;             // 處理OR指令                              
      case OP_XOR: R[ra] = R[rb] ^ R[rc]; break;             // 處理XOR指令                             
      case OP_ROL: R[ra] = ROL(R[rb],c5); break;             // 處理ROL指令                             
      case OP_ROR: R[ra] = ROR(R[rb],c5); break;             // 處理ROR指令                             
      case OP_SHL: R[ra] = SHL(R[rb],c5); break;             // 處理SHL指令                             
      case OP_SHR: R[ra] = SHR(R[rb],c5); break;             // 處理SHR指令                             
      case OP_JEQ: if (Z==1) PC += c24; break;               // 處理JEQ指令 Z=1
      case OP_JNE: if (Z==0) PC += c24; break;               // 處理JNE指令 Z=0 
      case OP_JLT: if (N==1&&Z==0) PC += c24; break;         // 處理JLT指令 NZ=10 
      case OP_JGT: if (N==0&&Z==0) PC += c24; break;         // 處理JGT指令 NZ=00
      case OP_JLE:                                           // 處理JLE指令 NZ=10 or 01
           if ((N==1&&Z==0)||(N==0&&Z==1)) PC+=c24; break;
      case OP_JGE:                                           // 處理JGE指令 NZ=00 or 01
           if ((N==0&&Z==0)||(N==0&&Z==1)) PC+=c24; break;
      case OP_JMP: PC+=c24; break;                           // 處理JMP指令                             
      case OP_SWI: LR = PC; PC=c24; break;                   // 處理SWI指令                             
      case OP_JSUB:LR = PC; PC+=c24; break;                  // 處理JSUB指令                            
      case OP_RET: if (LR<0) stop=TRUE; else PC=LR; break;   // 處理RET指令                             
      case OP_PUSH:SP-=4; StoreInt32(R[ra], m, SP); break;   // 處理PUSH指令                            
      case OP_POP:{//LoadInt32(R[ra], m, SP); 
         LoadInt32void(&R[ra],&m[SP]);
         SP+=4;
      } break;    // 處理POP指令                             
      case OP_PUSHB:SP--; StoreByte(R[ra], m, SP); break;    // 處理PUSH指令                            
      case OP_POPB:LoadByte(R[ra], m, SP); SP++; break;      // 處理POPB指令                            
      default: printf("Error:invalid op (%02x) ", op);              
      
                        // Cpu0Dump(cpu0);                                    
    }                                                                                                 
    sprintf(buffer, "PC=%08x IR=%08x SW=%08x R[%02d]=0x%08x=%d\n", // 印出 PC, IR, R[ra]暫存器的值,以利觀察
                         PC,      IR,     SW,  ra,    R[ra],R[ra]);

   // Cpu0Dump(cpu0);                                // 傾印暫存器
    strToUpper(buffer);
    printf(buffer);
  }                                                                                                   
}                                                                                       
                                                                                        
void Cpu0Dump(Cpu0 *cpu0) {                                  // 印出所有暫存器                           
  printf("\n===CPU0 dump registers===\n");                                                            
  printf("IR =0x%08x=%d\n", cpu0->IR, cpu0->IR);             // 印出 IR                                                                           
  int i;                                                                   
  for (i=0; i<16; i++)                                       // 印出 R0..R15  
    printf("R[%02d]=0x%08x=%d\n",i,cpu0->R[i],cpu0->R[i]);
}

Assembler

追到這邊還不能確定我們的 cpu 有沒有錯
簡化分析
sum = 10;

return sum;

我們做最簡化的帶入數值給 sum
原本程式碼
    case 'L' :                                                                                      
      sscanf(args, "R%d %s", &ra, p2);                                        
      if (strHead(p2, "[")) {                                                  
        sscanf(p2, "[R%d+%s]", &rb, pt);                                       
        if (sscanf(pt, "R%d", &rc)<=0)                                         
          sscanf(pt, "%d", &cx);                                               
      } else if (sscanf(p2, "%d", &cx)>0) {                                    
      } else {                                                             
        AsmCode *labelCode = HashTableGet(a->symTable, p2);                    
        cx = labelCode->address - pc;                                      
        rb = 15; // R[15] is PC                                            
      }                                                                         
      sprintf(cxCode, "%8x", cx);                                              
      sprintf(objCode, "%2x%x%x%s", code->opCode, ra, rb, &cxCode[4]);         
      break;     
修改後,應該只是應急的方式不過我們的 ASM 確實可以讀的到值,
    case 'L' :                                                                                      
      sscanf(args, "R%d %s", &ra, p2);          
      int p23= atoi(p2);       
              // printf("%d\n",p23)      ;                             
      if (strHead(p2, "[")) {                                                  
        sscanf(p2, "[R%d+%s]", &rb, pt);                                       
        if (sscanf(pt, "R%d", &rc)<=0)                                         
          sscanf(pt, "%d", &cx);              
                                   
      }  else {                                                             
        AsmCode *labelCode = HashTableGet(a->symTable, p2);                    
        cx = labelCode->address - pc;                                      
        rb = 15; // R[15] is PC                                            
      }      
      if (p23 >=0 && cx == 0){
       sscanf(p2, "%d", &cx);   
           printf("%d\n",p23)      ;   
      }                   
                                               
      sprintf(cxCode, "%8x", cx);                                              
      sprintf(objCode, "%2x%x%x%s", code->opCode, ra, rb, &cxCode[4]);         
      break;                                                               
    case 'A' :    

可以發現我們的這邊 間接定址已經出來了。
那麼開始排除 CPU ,使用我們寫好的 CPU 去 RUN 我們的 OBJ 檔

可以發現 確實不是我們的 CPU 問題,那麼問題就是 在 ASM 產生 OBJ檔 無法 間接定址的問題。
我們用複雜一點的
sum = 0;
for (i=0; i<10; i++)
{
  sum = sum  +2;
}
return sum;

BASH ROOT@ /c0c test.c0 test.asm
=AsmFile:test.asm==
LDI R1 0
ST R1 sum
LDI R1 0
ST R1 i
FOR0:
LD R1 i
LDI R2 10
CMP R1 R2
JGE _FOR0
LD R1 sum
LDI R2 2
ADD R3 R1 R2
ST R3 T0
LD R1 T0
ST R1 sum
LD R1 i
LDI R2 1
ADD R3 R1 R2
ST R3 i
JMP FOR0
_FOR0:
LD R1 sum
RET
sum: RESW 1
i: RESW 1
T0: RESW 1
BASH ROOT@ /as0 test.asm test.obj
=PASS2==
0
0000 LDI R1 0 L 8 08100000
0004 ST R1 SUM L 1 011F004C
0
0008 LDI R1 0 L 8 08100000
000C ST R1 I L 1 011F0048
0010 FOR0: FF
0010 LD R1 I L 0 001F0044
10
0014 LDI R2 10 L 8 0820000A
0018 CMP R1 R2 A 10 10120000
001C JGE _FOR0 J 25 2500002C
0020 LD R1 SUM L 0 001F0030
2
0024 LDI R2 2 L 8 08200002
0028 ADD R3 R1 R2 A 13 13312000
0
002C ST R3 T0 L 1 01300000
0
0030 LD R1 T0 L 0 00100000
0034 ST R1 SUM L 1 011F001C
0038 LD R1 I L 0 001F001C
1
003C LDI R2 1 L 8 08200001
0040 ADD R3 R1 R2 A 13 13312000
0044 ST R3 I L 1 013F0010
0048 JMP FOR0 J 26 26FFFFC4
004C _FOR0: FF
004C LD R1 SUM L 0 001F0004
0050 RET J 2C 2C000000
0054 SUM: RESW 1 D F0 00000000
0058 I: RESW 1 D F0 00000000
005C T0: RESW 1 D F0 00000000
BASH ROOT@ /vm0 test.obj
=CPU0 dump registers=
IR =0x2c000000=738197504
R[00]=0x00000000=0
R[01]=0x00000014=20
R[02]=0x0000000a=10
R[03]=0x0000000a=10
R[04]=0x00000000=0
R[05]=0x00000000=0
R[06]=0x00000000=0
R[07]=0x00000000=0
R[08]=0x00000000=0
R[09]=0x00000000=0
R[10]=0x00000000=0
R[11]=0x00000000=0
R[12]=0x40000000=1073741824
R[13]=0x00000000=0
R[14]=0xffffffff=-1

可以看到我們的 CPU 正常 WORK ,看來解決了書中 10 年前的 BUG ?XD 還是那時候編譯環境還可以正確運行也說不定。

Sunday, March 15, 2020

針對編譯器一次分析

實際上我們的編譯器要從高階語言 到 組合語言的過程
要經過
掃描器(Lexer)
剖析器(Parser)
語意分析 (Semarntic Analysis)
中間碼產生 (P-code)
最佳化(Optimziztion)
產生組合語言 (Asm Generator)
那麼我們來跟蹤一下老師的程式碼,來看看整個流程大致上是如何

INPUT

sum = 0;
for (i=0; i<=10; i++)
{
  sum = sum + i;
}

return sum;

Complier.c

這邊可以看到我們的主程式去讀檔,然後先交給 parser產生 語法樹
然後我們的generate 再把我們的語法樹給 轉為 asm 。
#include "Parser.h"
#include "Generator.h"

void compile(char *cFile, char *asmFile) {     // 編譯器主程式                  
  printf("compile file:%s\n", cFile, asmFile);                               
  char *cText = newFileStr(cFile);             //   讀取檔案到 cText 字串中。   
  Parser *parser = parse(cText);               //   剖析程式 (cText) 轉為語法樹 
  generate(parser->tree, asmFile);             //   程式碼產生                  
  ParserFree(parser);                          //   釋放記憶體                  
  freeMemory(cText);
} 

Parser.c

我們單看 Parser.c
這邊可以看到我們的 新增了一個 p指標 並調用 ParserNew 去初始化我們的 指標 *p 並呼叫我們的ParserParse 開始解析
#include "Parser.h"

Parser *parse(char *text) {        // 剖析器的主要函數          
  Parser *p=ParserNew();           // 建立剖析器       
  ParserParse(p, text);            // 開始剖析         
  return p;                        // 傳回剖析器       
}

ParserNew() ParserFree()

這邊跟前一文章組譯器原理差不多新增一個串列,這邊看到我們的 stack 是我們的 剖析堆疊。
另一個則是釋放記憶體
Parser *ParserNew() {
  Parser *parser = ObjNew(Parser, 1);
  parser->tokens = NULL;
  parser->tree = NULL;
  parser->stack = ArrayNew(10);
  return parser;
}

void ParserFree(Parser *parser) {
  ArrayFree(parser->tokens, strFree);
  ArrayFree(parser->stack, NULL);
  TreeFree(parser->tree);
  ObjFree(parser);
}

ParserParse

這邊目前看來是我們還不太清楚這邊要幹嘛先看tokenize
void ParserParse(Parser *p, char *text) {                 // 剖析物件的主函數            
  printf("======= tokenize =======\n");                   //   首先呼叫掃描器的主函數 tokenize() 將程式轉換為詞彙串列
  p->tokens = tokenize(text);                             
  printTokens(p->tokens);                                                     
  p->tokenIdx = 0;                                                                    
  printf("======= parsing ========\n");                                       
  p->tree = parseProg(p);                                 // 開始剖析 PROG = BaseList
  if (p->stack->count != 0) {                             // 如果剖析完成後堆疊是空的,那就是剖析成功 
    printf("parse fail:stack.count=%d", p->stack->count); //   否則就提示錯誤訊息
    error();                                                               
  }
}

Lexer 掃描器

tokenize

這邊發現我們的tokenize 位於 Scanner.c
也就說在我們生成語法樹的時候,我們要先針對我們能不能判斷當前這個token 是變數 是迴圈 還是數字 也就是說 我們要把她給 特殊標記
也就是說
printf("%d",30)
就會被拆成
上面第一排是 token
下面那一排是 我們的 token 進行 標記後詞彙標記後,這會有助與我們的剖析器作分析。
token-------------------------
printf(“%d”,30)
id(string,number)
type-------------------------
判斷是不是變數像我們的 c語言 變數定義就是開頭不能是數字。
可以接受 _ 這類作為開頭。
可以看到我們這邊都是在判斷 是不是 特殊 像是 Operator或是
數字或是英文字母。
所以我們首先來看
Array *tokenize(char *text)
{ // 將程式轉換成一個一個的詞彙
  Array *tokens = ArrayNew(10);
  Scanner *scanner = ScannerNew(text);
  char *token = NULL;
  while ((token = ScannerScan(scanner)) != NULL)
  { // 不斷取出下一個詞彙,直到程式字串結束為止
    ArrayAdd(tokens, newStr(token));
   // printf("token=%s\n", token);
  }
  ScannerFree(scanner);
  return tokens;
}
下面我們針對 ScannerScan 去做分析
我們看到比較關鍵的
next() 和 ch();
#define ch() (scanner->text[scanner->textIdx])
#define next() (scanner->textIdx++)
實際上就是 Scanner.c 裡面定義好的函數,主要就是對我們讀入的 當前 text 去做拆分
假設遇到是數字,英文 也可能會遇到運算子 假設符合規則就繼續讀下一個字元,當讀完一個串一個 token將會返回 加入我們的 tokens 裡面的 list。
char *ScannerScan(Scanner *scanner) {                                           // 掃描下一個詞彙                           
  while (strMember(ch(), SPACE))                                                // 忽略空白                                 
    next();                                                                                                              
  if (scanner->textIdx >= scanner->textLen)                                     // 檢查是否超過範圍                         
    return NULL;                                                                                                         
  char c = ch();                                                                // 取得下一個字元                           
  int begin = scanner->textIdx;                                                 // 記住詞彙開始點                           
  if (c == '\"') { // string = ".."                                             // 如果是 ",代表字串開頭,                 
    next(); // skip begin quote "                                                 
    while (ch() != '\"') next();                                                // 一直讀到下一個 " 符號為止。                                 
    next(); // skip end quote "                                                                                          
  } else if (strMember(c, OP)) { // OP , ex : ++, --, <=, >=, ...               // 如果是OP(+-*/<=>!等符號)                 
    while (strMember(ch(), OP)) next();                                         // 一直讀到不是OP為止                     
  } else if (strMember(c, DIGIT)) { // number, ex : 312, 77568, ...             // 如果是數字                               
    while (strMember(ch(), DIGIT)) next();                                      // 一直讀到不是數字為止                   
  } else if (strMember(c, ALPHA)) { // name, ex : int, sum, i, for, if, ....    // 如果是英文字母                           
    while (strMember(ch(), ALPHA) || strMember(ch(), DIGIT)) next();            // 一直讀到不是英文字母 (或數字)為止 (ex: x1y2z)
  } else // some other symbol, such as #                                                         
    next();                                                                     // 否則,傳回單一字元                                                                
  strSubstr(scanner->token, scanner->text, begin, scanner->textIdx-begin);      // 設定token為(begin…textIdx) 之間的子字串
  return scanner->token;                                                        // 傳回token詞彙
}      
仔細觀察的話,

可以看到我們的這個部分其實就是在做
把我的token 額外給他設定一個 type 的屬性也就是
token=sum , type=id
token== , type==
token=0 , type=number
token=; , type=;
token=return , type=return
token=sum , type=id
token=; , type=;

char *tokenToType(char *token)
{                                      // 判斷並取得 token的型態
  if (strPartOf(token, KEYWORDS))      //   如果是關鍵字 if, for, …
    return token;                      //   型態即為該關鍵字
  else if (token[0] == '\"')           // 如果以符號 " 開頭,則
    return STRING;                     //   型態為 STRING
  else if (strMember(token[0], DIGIT)) // 如果是數字開頭,則
    return NUMBER;                     //   型態為 NUMBER
  else if (strMember(token[0], ALPHA)) // 如果是英文字母開頭,則
    return ID;                         //   型態為 ID
  else                                 // 否則 (像是 +,-,*,/,>,<,….)
    return token;                      //   型態即為該 token
}

void printTokens(Array *tokens)
{
 //printf("tokens->count = %d\n", tokens->count);
  int i;
  for (i = 0; i < tokens->count; i++)
  {
    char *token = tokens->item[i];
    printf("token=%s , type=%s\n", token, tokenToType(token));
  }
}

Parser 剖析器

在經過了我們的掃描器之後我們要來把我們的 TOKEN 掃成 list 後 現在要把它再次處理成 一棵語法樹
假設處理過後我們的 list 最終輸出會長成這樣,仔細看的話可以看到他是有level的也就是縮排的格式。
那麼我們就已我們最上面開始的 input 去進行處理。
======= tokenize =======
token=sum , type=id     
token== , type==        
token=0 , type=number   
token=; , type=;        
token=for , type=for    
token=( , type=(        
token=i , type=id       
token== , type==
token=0 , type=number
token=; , type=;
token=i , type=id
token=<= , type=<=
token=10 , type=number
token=; , type=;
token=i , type=id
token=++ , type=++
token=) , type=)
token={ , type={
token=sum , type=id
token== , type==
token=sum , type=id
token=+ , type=+
token=i , type=id
token=; , type=;
token=} , type=}
token=return , type=return
token=sum , type=id
token=; , type=;
======= parsing ========
+PROG
 +BaseList
  +BASE
   +STMT
     idx=0, token=sum, type=id
     idx=1, token==, type==
    +EXP
      idx=2, token=0, type=number
    -EXP
   -STMT
    idx=3, token=;, type=;
  -BASE
  +BASE
   +FOR
     idx=4, token=for, type=for
     idx=5, token=(, type=(
    +STMT
      idx=6, token=i, type=id
      idx=7, token==, type==
     +EXP
       idx=8, token=0, type=number
     -EXP
    -STMT
     idx=9, token=;, type=;
    +COND
     +EXP
       idx=10, token=i, type=id
     -EXP
      idx=11, token=<=, type=<=
     +EXP
       idx=12, token=10, type=number
     -EXP
    -COND
     idx=13, token=;, type=;
    +STMT
      idx=14, token=i, type=id
      idx=15, token=++, type=++
    -STMT
     idx=16, token=), type=)
    +BLOCK
      idx=17, token={, type={
     +BaseList
      +BASE
       +STMT
         idx=18, token=sum, type=id
         idx=19, token==, type==
        +EXP
          idx=20, token=sum, type=id
          idx=21, token=+, type=+
          idx=22, token=i, type=id
        -EXP
       -STMT
        idx=23, token=;, type=;
      -BASE
     -BaseList
      idx=24, token=}, type=}
    -BLOCK
   -FOR
  -BASE
  +BASE
   +STMT
     idx=25, token=return, type=return
     idx=26, token=sum, type=id
   -STMT
    idx=27, token=;, type=;
  -BASE
 -BaseList
-PROG

parseProg

一開始進入點我們算是最開始的語法樹根到
parseBaseList ->parseBase
可以看到我們目前只有編寫兩種 一個是變數賦予值和 For
我們先以變數來
sum=0; 看看會發生什麼事 ,假設以這種情況就是從ParseStmt § 開始跑。
// PROG = BaseList
Tree *parseProg(Parser *p) {                // 剖析 PROG=BaseList 規則    
  push(p, "PROG");                                                      
  parseBaseList(p);                         // 建立 PROG 的樹根          
  return pop(p, "PROG");                    //  剖析 BaseList,            
}                                           // 取出 PROG 的剖析樹    

// BaseList= (BASE)*                        // 剖析 BaseList =(BASE)* 規則  
void parseBaseList(Parser *p) {                                         
  push(p, "BaseList");                      // 建立 BaseList 的樹根      
  while (!isEnd(p) && !isNext(p, "}"))      //  剖析 BASE,直到程式結束或碰到 } 為止
      parseBase(p);                                        
  pop(p, "BaseList");                       // 取出 BaseList 的剖析樹    
}

// BASE = FOR | STMT ';'
void parseBase(Parser *p) {                 // 剖析 BASE = FOR|STMT 規則                           
  push(p, "BASE");                                                                                
  if (isNext(p, "for"))                     // 建立 BASE 的樹根                                     
      parseFor(p);                          // 如果下一個詞彙是 for                                  
  else {                                    //  根據 FOR 規則進行剖析                                
      parseStmt(p);                         // 否則                                                  
      next(p, ";");                         //  根據 STMT 規則進行剖析                               
  }                                                                                               
  pop(p, "BASE");                           // 取出 BASE 的剖析樹                                  
}        

parseStmt

這邊可以看到我們tokenize list 裡面的 type 是 id 所以會走 else 那個條件,
可以知道我們需要走next 這個函數 才能進到下一層 parserExp§
這時候我們把這個 next() 拿出來看一下。
// STMT = return id | id '=' EXP  | id OP1
void parseStmt(Parser *p) {
  push(p, "STMT");
  if (isNext(p, "return")) {
    next(p, "return");
    next(p, "id");
  } else {
    next(p, "id");
    if (isNext(p, "="))  { // id '=' EXP   --> ASSIGN
      next(p, "=");
      parseExp(p);
    } else              // id OP1
      next(p, OP1);
  }
  pop(p, "STMT");
}

next()

可以看到假設在上述剛剛的情況下可以得知我們的傳入的p 中間的最主要判斷 isNext(p, “id”)
這邊可以看
        
char *next(Parser *p, char *pTypes) {                         // 檢查下一個詞彙的型態                                          
  char *token = nextToken(p);                                 // 取得下一個詞彙                                               
  if (isNext(p, pTypes)) {                                    // 如果是pTypes型態之一                                         
    char *type = tokenToType(token);                          //   取得型態                                                    
    Tree *child = TreeNew(type, token);                       //   建立詞彙節點(token,type)                                    
    Tree *parentTree = ArrayPeek(p->stack);                   //   取得父節點,                                                
    TreeAddChild(parentTree, child);                          //   加入父節點成為子樹                                          
    printf("%s idx=%d, token=%s, type=%s\n",                  //   印出詞彙以便觀察                                            
      level(p),p->tokenIdx,token,type);                                                                                     
    p->tokenIdx++;                                            //   前進到下一個節點                                            
    return token;                                             //   傳回該詞彙                                                  
  } else {                                                    // 否則(下一個節點型態錯誤)                                     
    printf("next():%s is not type(%s)\n", token, pTypes);     //   印出錯誤訊息                                                
    error();                                                                                                                
    p->tokenIdx++;                                            //  前進到下一個節點                                                                                                          
    return NULL;                                              
  }                                                                                                                         
}           
他其實是在判斷當前的p->tokenIdx 指向哪個 index 所以是 0 開始
也就是 p->tokens->item[p->tokenIdx];
那就是意味著 第一個 tokens[0] 的型態是哪一個 type 和是否真的符合我們type 的格式。
char* nextToken(Parser *p) {
  return (char*) p->tokens->item[p->tokenIdx];
}
BOOL isNext(Parser *p, char *pTypes) {
  char *token = nextToken(p); 
  if (token == NULL) return FALSE;
  char *type = tokenToType(token);
  char tTypes[MAX_LEN+1];
  sprintf(tTypes, "|%s|", pTypes);
  if (strPartOf(type, tTypes))
    return TRUE;
  else
    return FALSE;
}
再繼續往裡面看還可以看到strPartOf
ASSERT
裡面就是寫假設有其中一個條件發生錯誤好像可以讓程式強制停止
也就是假設 我們的 item 裡面的值是為空
strstr
這個函式會在 str 中尋找 substr,一樣回傳 substr 的第一個出現位址,如果沒找到的話則是 NULL。
這樣的話就是去檢查 我們傳進來的 的 token 被校正成 |%s| 也就可能變成 |EXP| |STMT| …,後續再講 語意分析和 產生中間碼 會有關係
格式化後 回到剛剛的
BOOL strPartOf(char *token, char *set) {
  ASSERT(token != NULL && set != NULL);
  ASSERT(strlen(token) < 100);
  char ttoken[100];
  sprintf(ttoken, "|%s|", token);
  return (strstr(set, ttoken)!=NULL);
}
isNext () 這邊我們大概就知道意思了
我們直接去讀當前這個 p->token->item 裡面的 type
然後 假設找的到 就往下 ,找不到就 return;
然後我們的 產生一個臨時的變數 type 然後 校正格式為 |%s| 因為我們傳過來的是 id 所以可能 最終輸出就是 |id|

BOOL isNext(Parser *p, char *pTypes) {
  char *token = nextToken(p); 
  if (token == NULL) return FALSE;
  char *type = tokenToType(token);
  char tTypes[MAX_LEN+1];
  sprintf(tTypes, "|%s|", pTypes);
  if (strPartOf(type, tTypes))
    return TRUE;
  else
    return FALSE;
}
這邊可以看到 我們把我們的 type 他又跑去呼叫tokenToType(token); 這邊檢查型態也就是
Scanner.c
char STRING[] = "string";
char NUMBER[] = "number";
char ID[] = "id";
char KEYWORDS[] = "|if|for|while|return|";
char OP1[] = "|++|--|";
char OP2[] = "|+|-|*|/|";
char COND_OP[] = "|==|!=|<=|>=|<|>|";
char ITEM[] = "|id|number|string|";
char OP[] = "+-*/<=>!";

char *tokenToType(char *token)
{                                      // 判斷並取得 token的型態
  if (strPartOf(token, KEYWORDS))      //   如果是關鍵字 if, for, …
    return token;                      //   型態即為該關鍵字
  else if (token[0] == '\"')           // 如果以符號 " 開頭,則
    return STRING;                     //   型態為 STRING
  else if (strMember(token[0], DIGIT)) // 如果是數字開頭,則
    return NUMBER;                     //   型態為 NUMBER
  else if (strMember(token[0], ALPHA)) // 如果是英文字母開頭,則
    return ID;                         //   型態為 ID
  else                                 // 否則 (像是 +,-,*,/,>,<,….)
    return token;                      //   型態即為該 token
}

最終他呼叫了strPartOf ()函數 去做 可能在編譯的過程中,
出現異常的檢查ASSERT和 用 strstr() 這個函數去查 我們的 (type, tTypes) type有沒有tTypes在裡面 當然他裡面還做了 格式化一次也就是
包起來 sprintf(ttoken, “|%s|”, token);
最後只有兩種情況 就是
找的到我們的 函數型態和 找不到我們函數型態也就是
if (strPartOf(type, tTypes))
    return TRUE;
  else
    return FALSE;
這樣的話我們的又要回到主函數 next了

返回next

那麼我們知道返回是 true 和 false 我們就來分析一下會發生什麼事。
char *next(Parser *p, char *pTypes) {                         // 檢查下一個詞彙的型態                                          
  char *token = nextToken(p);                                 // 取得下一個詞彙                                               
  if (isNext(p, pTypes)) {                                    // 如果是pTypes型態之一                                         
    char *type = tokenToType(token);                          //   取得型態                                                    
    Tree *child = TreeNew(type, token);                       //   建立詞彙節點(token,type)                                    
    Tree *parentTree = ArrayPeek(p->stack);                   //   取得父節點,                                                
    TreeAddChild(parentTree, child);                          //   加入父節點成為子樹                                          
    printf("%s idx=%d, token=%s, type=%s\n",                  //   印出詞彙以便觀察                                            
      level(p),p->tokenIdx,token,type);                                                                                     
    p->tokenIdx++;                                            //   前進到下一個節點                                            
    return token;                                             //   傳回該詞彙                                                  
  } else {                                                    // 否則(下一個節點型態錯誤)                                     
    printf("next():%s is not type(%s)\n", token, pTypes);     //   印出錯誤訊息                                                
    error();                                                                                                                
    p->tokenIdx++;                                            //  前進到下一個節點                                                                                                          
    return NULL;                                              
  }                                                                                                                         
}         

true

返回成功後,就是代表當前這個 token 是 符合我們傳進去的參數 id
也就是 next(p, “id”); ,下面它們還有提到節點也就是我們要生成

類似這樣的語法樹
  if (isNext(p, pTypes)) {                                    // 如果是pTypes型態之一                                         
    char *type = tokenToType(token);                          //   取得型態                                                    
    Tree *child = TreeNew(type, token);                       //   建立詞彙節點(token,type)                                    
    Tree *parentTree = ArrayPeek(p->stack);                   //   取得父節點,                                                
    TreeAddChild(parentTree, child);                          //   加入父節點成為子樹                                          
    printf("%s idx=%d, token=%s, type=%s\n",                  //   印出詞彙以便觀察                                            
      level(p),p->tokenIdx,token,type);                                                                                     
    p->tokenIdx++;                                            //   前進到下一個節點                                            
    return token;                                             //   傳回該詞彙                                                  
  } 
不過目前這個例子 只是 sum = 0;這個情況 也就是
這一塊,這邊可能要去買一下書才知道 xdd,目前只能靠慢慢分析
======= parsing ========
+PROG
 +BaseList
  +BASE
   +STMT
     idx=0, token=sum, type=id
     idx=1, token==, type==
    +EXP
      idx=2, token=0, type=number
    -EXP
   -STMT
-------------------------
可能是目前累積在 stack 的深度?
char* level(Parser *p) {
  return strSpaces(p->stack->count);
}
因為他一直附加在 主節點 應該是
    Tree *parentTree = ArrayPeek(p->stack);                   //   取得父節點,                                                
    TreeAddChild(parentTree, child);                          //   加入父節點成為子樹    
也可以看到他有print分層 level§ ,這邊跟我用 gcc plugin 分析跑的東西很像 xd

false

不是直接略
else {                                                    // 否則(下一個節點型態錯誤)                                     
    printf("next():%s is not type(%s)\n", token, pTypes);     //   印出錯誤訊息                                                
    error();                                                                                                                
    p->tokenIdx++;                                            //  前進到下一個節點                                                                                                          
    return NULL;                                              
  } 

返回parseStmt

過總而言之 無論如何 p->tokenIdx++ 勢必會往下一個 item 移動
那我們的 目前 item index = 1
也就是
======= tokenize =======
token=sum , type=id     
token== , type==        
第二個 =
// STMT = return id | id '=' EXP  | id OP1
void parseStmt(Parser *p) {
  push(p, "STMT");
  if (isNext(p, "return")) {
    next(p, "return");
    next(p, "id");
  } else {
    next(p, "id");
    if (isNext(p, "="))  { // id '=' EXP   --> ASSIGN
      next(p, "=");
      parseExp(p);
    } else              // id OP1
      next(p, OP1);
  }
  pop(p, "STMT");
}
push 可以知道 他其實就是在對我們的 stack 去做堆疊回到我們主程式就明瞭了
Tree* push(Parser *p, char* pType) {                          // 建立 pType 型態的子樹,推入堆疊中                                                              
  printf("%s+%s\n", level(p), pType);                                                      
  Tree* tree = TreeNew(pType, "");                                                                                          
  ArrayPush(p->stack, tree);                                                                                                
  return tree;                                                                                                              
}            
判斷目前是不是 return 不是結束的話 就判斷是不是 id
無論是不是id 都往下一個 token 前進
// STMT = return id | id '=' EXP  | id OP1
void parseStmt(Parser *p) {
  push(p, "STMT");
  if (isNext(p, "return")) {
    next(p, "return");
    next(p, "id");
  } else {
    next(p, "id");
    if (isNext(p, "="))  { // id '=' EXP   --> ASSIGN
      next(p, "=");
      parseExp(p);
    } else              // id OP1
      next(p, OP1);
  }
  pop(p, "STMT");
}
這邊可以看到老師就直接檢查 是不是 “=” 了
在呼叫我們的parseExp§;
  if (isNext(p, "="))  { // id '=' EXP   --> ASSIGN
      next(p, "=");
      parseExp(p);
    } else              // id OP1
      next(p, OP1);

true

 next(p, "id");
    if (isNext(p, "="))  { // id '=' EXP   --> ASSIGN
      next(p, "=");
      parseExp(p);
    } 
假設是的話 再往下一個推 也就意味著
目前已經到我們的 number index = 2
======= tokenize =======
token=sum , type=id     
token== , type==        
token=0 , type=number   

parseExp

可以看他他就在 往下一個移動
char OP2[] = "|+|-|*|/|";
char ITEM[]= "|id|number|string|";
假設是 是不是 item 也就是是不是等於 變數或者 string id之類的 無論如何都往前推下一個 token index
並且將 index 的 token 和 type 給印出來
他又判斷 是否是 Operator
// EXP = ITEM [+-*/] ITEM | ITEM
void parseExp(Parser *p) {
  push(p, "EXP");
  next(p, ITEM);
  if (isNext(p, OP2)) {
      next(p, OP2);
      next(p, ITEM);
  }
  pop(p, "EXP");
}
======= parsing ========
+PROG
 +BaseList
  +BASE
   +STMT
     idx=0, token=sum, type=id
     idx=1, token==, type==
    +EXP
      idx=2, token=0, type=number

false

他這邊只是單純判斷
 else              // id OP1
      next(p, OP1);
char OP1[] = "|++|--|";
到這邊我們已經可以生成一棵 語法樹了, For 我就不分析了 大同小異
整體長起來會像這樣
======= parsing ========
+PROG
 +BaseList
  +BASE
   +STMT
     idx=0, token=sum, type=id
     idx=1, token==, type==
    +EXP
      idx=2, token=0, type=number
    -EXP
   -STMT
    idx=3, token=;, type=;
  -BASE
  +BASE
   +FOR
     idx=4, token=for, type=for
     idx=5, token=(, type=(
    +STMT
      idx=6, token=i, type=id
      idx=7, token==, type==
     +EXP
       idx=8, token=0, type=number
     -EXP
    -STMT
     idx=9, token=;, type=;
    +COND
     +EXP
       idx=10, token=i, type=id
     -EXP
      idx=11, token=<=, type=<=
     +EXP
       idx=12, token=10, type=number
     -EXP
    -COND
     idx=13, token=;, type=;
    +STMT
      idx=14, token=i, type=id
      idx=15, token=++, type=++
    -STMT
     idx=16, token=), type=)
    +BLOCK
      idx=17, token={, type={
     +BaseList
      +BASE
       +STMT
         idx=18, token=sum, type=id
         idx=19, token==, type==
        +EXP
          idx=20, token=sum, type=id
          idx=21, token=+, type=+
          idx=22, token=i, type=id
        -EXP
       -STMT
        idx=23, token=;, type=;
      -BASE
     -BaseList
      idx=24, token=}, type=}
    -BLOCK
   -FOR
  -BASE
  +BASE
   +STMT
     idx=25, token=return, type=return
     idx=26, token=sum, type=id
   -STMT
    idx=27, token=;, type=;
  -BASE
 -BaseList
-PROG

語意分析 Semarntic Analysis

在經過了上述兩個階段後我們要來分析最後一個
generate(parser->tree, asmFile);
void compile(char *cFile, char *asmFile) {     // 編譯器主程式                  
  printf("compile file:%s\n", cFile, asmFile);                               
  char *cText = newFileStr(cFile);             //   讀取檔案到 cText 字串中。   
  Parser *parser = parse(cText);               //   剖析程式 (cText) 轉為語法樹 
  generate(parser->tree, asmFile);             //   程式碼產生                  
  ParserFree(parser);                          //   釋放記憶體                  
  freeMemory(cText);
}

generate

可以看到他最主要的GenCode 我們直接看過去吧
// 程式產生器的主要函數。
void generate(Tree *tree, char *asmFile) {      // 將剖析樹 tree 轉為組合語言檔 asmFile
  char nullVar[100]="";                                                             
  Generator *g = GenNew();                      // 開啟組合語言檔以便輸出              
  g->asmFile = fopen(asmFile, "w");                                                 
  printf("=====PCODE=====\n");                  // 產生程式碼                          
  GenCode(g, tree, nullVar);                    // 產生資料宣告                        
  GenData(g);                                   // 關閉組合語言檔                      
  fclose(g->asmFile);                           // 釋放記憶體                          
  GenFree(g);                                   // 讀入組合語言檔並印出                
  char *asmText = newFileStr(asmFile);                                              
  printf("=====AsmFile:%s======\n", asmFile);                                       
  printf("%s\n", asmText);                      // 釋放記憶體                          
  freeMemory(asmText);
}

GenCode

可能會覺得這怎麼看,我們先縮小範圍
Tree* GenCode(Generator *g, Tree *node, char *rzVar) {                          // 遞迴產生節點 node 的程式碼         
  strcpy(nullVar, "");                                                                                             
  strcpy(rzVar, "");                                                                                               
  if (node == NULL) return NULL;                                                // 遞迴終止條件。                     
                                                                                                                   
  if (strEqual(node->type, "FOR")) {                                            // 處理 FOR 節點                      
    // FOR ::= 'for' '(' STMT ';' COND ';' STMT ')' BLOCK                                                
    char forBeginLabel[100], forEndLabel[100], condOp[100];                     
    Tree *stmt1 = node->childs->item[2],                                        // 取得子節點                         
         *cond  = node->childs->item[4],                                                                           
         *stmt2 = node->childs->item[6],                                                                           
         *block = node->childs->item[8];                                                                           
    GenCode(g, stmt1, nullVar);                                                 // 遞迴產生 STMT                             
    int tempForCount = g->forCount++;                                           // 設定FOR迴圈的                 
    sprintf(forBeginLabel, "FOR%d", tempForCount);                              //   進入標記                                
    sprintf(forEndLabel, "_FOR%d", tempForCount);                               //   離開標記            
    GenPcode(g, forBeginLabel, "", "", "", "");                                 // 中間碼:例如 FOR1:    
    GenCode(g, cond, condOp);                                                   // 遞迴產生 COND        
    char negOp[100];                                                                                  
    negateOp(condOp, negOp);                                                    // 互補運算negOp         
    GenPcode(g, "", "J", negOp, "", forEndLabel);                               // 中間碼:例如J > _FOR1 
    GenCode(g, block, nullVar);                                                 // 遞迴產生 BLOCK       
    GenCode(g, stmt2, nullVar);                                                 // 遞迴產生 STMT        
    GenPcode(g, "", "J", "", "", forBeginLabel);                                // 中間碼:例如J FOR1    
    GenPcode(g, forEndLabel, "", "", "", "");                                   // 中間碼:例如 _FOR1    
    return NULL;                                                                                      
  } else if (strEqual(node->type, "STMT")) {                                    // 處理 STMT 節點          
    //   STMT = return id | id '=' EXP | id ('++'|'--')                                              
    Tree *c1 = node->childs->item[0];                                           //   取得子節點              
    if (strEqual(c1->type, "return")) {                                         // 處理 return 指令                                
      Tree *id = node->childs->item[1];                                                                   
      GenPcode(g, "", "RET", "", "", id->value);                                // 中間碼: 例如 RET sum      
    } else {                                                                                              
      Tree *id = node->childs->item[0];                                         //   取得子節點              
      Tree *op = node->childs->item[1];                                                                   
      if (strEqual(op->type, "=")) {                                            // 處理 id= EXP             
        // STMT 是 id '=' EXP                                                   //  取得子節點               
        Tree *exp = node->childs->item[2];                                                                
        char expVar[100];          
        GenCode(g, exp, expVar);                                                //  遞迴產生 EXP            
        printf("ha%s\n",expVar) ;
        printf("ha%s\n",id->value) ;
        GenPcode(g, "", "=", expVar, "", id->value);                            //  中間碼:例如 = 0 sum     
        HashTablePut(g->symTable, id->value, id->value);                        //  將 id 加入到符號表中                              
        strcpy(rzVar, expVar);                                                  //  傳回 EXP 的變數,例如 T0  
      } else { // STMT 是 id++ 或 id--,--> id OP1                              // 處理 id++ 或 id--         
        char addsub[100];                                                                                 
        if (strEqual(op->value, "++"))                                          //  如果是 id++              
          strcpy(addsub, "+");                                                  //   設定運算為 + 法         
        else                                                                    //  否則                     
          strcpy(addsub, "-");                                                  //   設定運算為 - 法         
        GenPcode(g, "", addsub, id->value, "1", id->value);                     //  中間碼:例如 ADD i, 1, i 
        strcpy(rzVar, id->value);                                               //  傳回id,例如 i           
      }                                                                         
    }                                                                           
  } else if (strEqual(node->type, "COND")) {                                    // 處理 COND 節點      
    // 處理判斷式 COND = EXP ('=='|'!='|'<='|'>='|'<'|'>') EXP                                
    Tree* op = node->childs->item[1];                                           // 取得子節點            
    char expVar1[100], expVar2[100];                                                                  
    GenCode(g, node->childs->item[0], expVar1);                                 //  遞迴產生 EXP       
    GenCode(g, node->childs->item[2], expVar2);                                 //  遞迴產生 EXP       
    GenPcode(g, "", "CMP", expVar1, expVar2, nullVar);                          //  中間碼:例如 CMP i,10 
    strcpy(rzVar, op->value); // 傳回布林運算子                                 //  傳回op,例如 >       
  } else if (strPartOf(node->type, "|EXP|")) {                                  // 處理 EXP
    // 處理運算式 EXP = ITEM ([+-*/] ITEM)*         
    
    Tree *item1 = node->childs->item[0];                                        // 取得子節點            
    char var1[100], var2[100], tempVar[100];        
    printf("%s\n" ,item1->value);                                                  
    GenCode(g, item1, var1);                                                    // 遞迴產生 ITEM
    if (node->childs->count > 1) {
      Tree* op = node->childs->item[1];                                        // 連續取得 (op ITEM)? 
      Tree* item2 = node->childs->item[2];                                                         
      GenCode(g, item2, var2);                                                  // 遞迴產生 ITEM        
      GenTempVar(g, tempVar);                                                   // 取得臨時變數,例如T0  
      GenPcode(g, "", op->value, var1, var2, tempVar);                          // 中間碼:例如 + sum i T0
      strcpy(var1, tempVar);                                                    // 傳回臨時變數,例如 T0  
    }                                                                                                 
    
    strcpy(rzVar, var1);                                                        // 傳回臨時變數,例如 T0  
  } else if (strPartOf(node->type, "|number|id|")) {                            // 處理 number, id 節點
    // 遇到變數或常數,傳回其 value 名稱。                                 
         
        printf("strha%s\n",node->value) ;                        
    strcpy(rzVar, node->value);                                                 // 直接傳回 id 或 number
  } else if (node->childs != NULL) {                                            // 其他情況           
    // 其他狀況,若有子代則遞回處理                                                                
    int i;                                                                                         
    for (i=0; i<node->childs->count; i++)                                       // 遞迴處理所有子節點
      GenCode(g, node->childs->item[i], nullVar);
  }
  return NULL;
}

以 sum = 0 ; 來看這例子
======= tokenize =======
token=sum , type=id     
token== , type==        
token=0 , type=number   
token=; , type=;     
======= parsing ========
+PROG
 +BaseList
  +BASE
   +STMT
     idx=0, token=sum, type=id
     idx=1, token==, type==
    +EXP
      idx=2, token=0, type=number
    -EXP
   -STMT
    idx=3, token=;, type=;
  -BASE
我們上次執行階段到這邊
在看我們的 GenCode裡面 可以得知 我們剛剛處理的宣告 sum = 0 ;
其實是在 STMT 節點
else if (strEqual(node->type, "STMT")) {                                    // 處理 STMT 節點          
    //   STMT = return id | id '=' EXP | id ('++'|'--')                                              
    Tree *c1 = node->childs->item[0];                                           //   取得子節點              
    if (strEqual(c1->type, "return")) {                                         // 處理 return 指令                                
      Tree *id = node->childs->item[1];                                                                   
      GenPcode(g, "", "RET", "", "", id->value);                                // 中間碼: 例如 RET sum      
    } else {                                                                                              
      Tree *id = node->childs->item[0];                                         //   取得子節點              
      Tree *op = node->childs->item[1];                                                                   
      if (strEqual(op->type, "=")) {                                            // 處理 id= EXP             
        // STMT 是 id '=' EXP                                                   //  取得子節點               
        Tree *exp = node->childs->item[2];                                                                
        char expVar[100];          
        GenCode(g, exp, expVar);                                                //  遞迴產生 EXP            
        GenPcode(g, "", "=", expVar, "", id->value);                            //  中間碼:例如 = 0 sum     
        HashTablePut(g->symTable, id->value, id->value);                        //  將 id 加入到符號表中                              
        strcpy(rzVar, expVar);                                                  //  傳回 EXP 的變數,例如 T0  
      }

可以直接看到 else
  Tree *id = node->childs->item[0];                                         //   取得子節點              
  Tree *op = node->childs->item[1];   
  if (strEqual(op->type, "=")) {                                            // 處理 id= EXP             
        // STMT 是 id '=' EXP                                                   //  取得子節點               
        Tree *exp = node->childs->item[2];                                                                
        char expVar[100];          
        GenCode(g, exp, expVar);                                                //  遞迴產生 EXP            
        GenPcode(g, "", "=", expVar, "", id->value);                            //  中間碼:例如 = 0 sum     
        HashTablePut(g->symTable, id->value, id->value);                        //  將 id 加入到符號表中                              
        strcpy(rzVar, expVar);                                                  //  傳回 EXP 的變數,例如 T0  
      }
我有加了幾行註解方便分析

Tree* GenCode(Generator *g, Tree *node, char *rzVar) {                          // 遞迴產生節點 node 的程式碼         
  strcpy(nullVar, "");                                                                                             
  strcpy(rzVar, "");                                                                                               
  if (node == NULL) return NULL;                                                // 遞迴終止條件。                     
                                                                                                                   
  if (strEqual(node->type, "FOR")) {                                            // 處理 FOR 節點                      
    // FOR ::= 'for' '(' STMT ';' COND ';' STMT ')' BLOCK                                                
    char forBeginLabel[100], forEndLabel[100], condOp[100];                     
    Tree *stmt1 = node->childs->item[2],                                        // 取得子節點                         
         *cond  = node->childs->item[4],                                                                           
         *stmt2 = node->childs->item[6],                                                                           
         *block = node->childs->item[8];                                                                           
    GenCode(g, stmt1, nullVar);                                                 // 遞迴產生 STMT                             
    int tempForCount = g->forCount++;                                           // 設定FOR迴圈的                 
    sprintf(forBeginLabel, "FOR%d", tempForCount);                              //   進入標記                                
    sprintf(forEndLabel, "_FOR%d", tempForCount);                               //   離開標記            
    GenPcode(g, forBeginLabel, "", "", "", "");                                 // 中間碼:例如 FOR1:    
    GenCode(g, cond, condOp);                                                   // 遞迴產生 COND        
    char negOp[100];                                                                                  
    negateOp(condOp, negOp);                                                    // 互補運算negOp         
    GenPcode(g, "", "J", negOp, "", forEndLabel);                               // 中間碼:例如J > _FOR1 
    GenCode(g, block, nullVar);                                                 // 遞迴產生 BLOCK       
    GenCode(g, stmt2, nullVar);                                                 // 遞迴產生 STMT        
    GenPcode(g, "", "J", "", "", forBeginLabel);                                // 中間碼:例如J FOR1    
    GenPcode(g, forEndLabel, "", "", "", "");                                   // 中間碼:例如 _FOR1    
    return NULL;                                                                                      
  } else if (strEqual(node->type, "STMT")) {                                    // 處理 STMT 節點          
    //   STMT = return id | id '=' EXP | id ('++'|'--')                                              
    Tree *c1 = node->childs->item[0];                                           //   取得子節點              
    if (strEqual(c1->type, "return")) {                                         // 處理 return 指令                                
      Tree *id = node->childs->item[1];                                                                   
      GenPcode(g, "", "RET", "", "", id->value);                                // 中間碼: 例如 RET sum      
    } else {                                                                                              
      Tree *id = node->childs->item[0];                                         //   取得子節點              
      Tree *op = node->childs->item[1];                                                                   
      if (strEqual(op->type, "=")) {                                            // 處理 id= EXP             
        // STMT 是 id '=' EXP                                                   //  取得子節點               
        Tree *exp = node->childs->item[2];                                                                
        char expVar[100];          
        GenCode(g, exp, expVar);                                                //  遞迴產生 EXP            
        printf("number or id name : %s\n",expVar) ;
        printf("value %s\n",id->value) ;
        GenPcode(g, "", "=", expVar, "", id->value);                            //  中間碼:例如 = 0 sum     
        HashTablePut(g->symTable, id->value, id->value);                        //  將 id 加入到符號表中                              
        strcpy(rzVar, expVar);                                                  //  傳回 EXP 的變數,例如 T0  
      } else { // STMT 是 id++ 或 id--,--> id OP1                              // 處理 id++ 或 id--         
        char addsub[100];                                                                                 
        if (strEqual(op->value, "++"))                                          //  如果是 id++              
          strcpy(addsub, "+");                                                  //   設定運算為 + 法         
        else                                                                    //  否則                     
          strcpy(addsub, "-");                                                  //   設定運算為 - 法         
        GenPcode(g, "", addsub, id->value, "1", id->value);                     //  中間碼:例如 ADD i, 1, i 
        strcpy(rzVar, id->value);                                               //  傳回id,例如 i           
      }                                                                         
    }                                                                           
  } else if (strEqual(node->type, "COND")) {                                    // 處理 COND 節點      
    // 處理判斷式 COND = EXP ('=='|'!='|'<='|'>='|'<'|'>') EXP                                
    Tree* op = node->childs->item[1];                                           // 取得子節點            
    char expVar1[100], expVar2[100];                                                                  
    GenCode(g, node->childs->item[0], expVar1);                                 //  遞迴產生 EXP       
    GenCode(g, node->childs->item[2], expVar2);                                 //  遞迴產生 EXP       
    GenPcode(g, "", "CMP", expVar1, expVar2, nullVar);                          //  中間碼:例如 CMP i,10 
    strcpy(rzVar, op->value); // 傳回布林運算子                                 //  傳回op,例如 >       
  } else if (strPartOf(node->type, "|EXP|")) {                                  // 處理 EXP
    // 處理運算式 EXP = ITEM ([+-*/] ITEM)*         
    
    Tree *item1 = node->childs->item[0];                                        // 取得子節點            
    char var1[100], var2[100], tempVar[100];        
    printf("EXP :%s\n" ,item1->value);         
    printf("EXP type:%s\n" ,item1->type);                                           
    GenCode(g, item1, var1);                                                    // 遞迴產生 ITEM
    if (node->childs->count > 1) {
      Tree* op = node->childs->item[1];                                        // 連續取得 (op ITEM)? 
      Tree* item2 = node->childs->item[2];                                                         
      GenCode(g, item2, var2);                                                  // 遞迴產生 ITEM        
      GenTempVar(g, tempVar);                                                   // 取得臨時變數,例如T0  
      GenPcode(g, "", op->value, var1, var2, tempVar);                          // 中間碼:例如 + sum i T0
      strcpy(var1, tempVar);                                                    // 傳回臨時變數,例如 T0  
    }                                                                                                 
    
    strcpy(rzVar, var1);                                                        // 傳回臨時變數,例如 T0  
  } else if (strPartOf(node->type, "|number|id|")) {                            // 處理 number, id 節點
    // 遇到變數或常數,傳回其 value 名稱。                                  
        printf("number or id :%s\n",node->value) ;                        
    strcpy(rzVar, node->value);                                                 // 直接傳回 id 或 number
  } else if (node->childs != NULL) {                                            // 其他情況           
    // 其他狀況,若有子代則遞回處理                                                                
    int i;                                                                                         
    for (i=0; i<node->childs->count; i++)                                       // 遞迴處理所有子節點
      GenCode(g, node->childs->item[i], nullVar);
  }
  return NULL;
}
整體輸出就會變這樣
因為我們要從 語意分析 再來才是 中間碼 再轉為 asm 組合語言,所以
=====PCODE=====
EXP :0
EXP type:number
number or id :0
number or id name : 0
value sum
         =    0         sum
我們可以在 分析生成 中間碼 之前看看發生了什麼事
         =    0         sum
我們來分析
else {                                                                                              
      Tree *id = node->childs->item[0];                                         //   取得子節點              
      Tree *op = node->childs->item[1];     
看到這邊可以知道我們其實是去抓
我們 tee index = 0 和 1
+PROG
 +BaseList
  +BASE
   +STMT
     idx=0, token=sum, type=id  <<<childs->item[0];
     idx=1, token==, type==     <<<childs->item[1];

我們針對
我們 op tree 他的 type == “=”
的情況下會發生什麼事
 if (strEqual(op->type, "=")) {                                            // 處理 id= EXP             
        // STMT 是 id '=' EXP                                                   //  取得子節點               
        Tree *exp = node->childs->item[2];                                                                
        char expVar[100];          
        GenCode(g, exp, expVar);                                                //  遞迴產生 EXP            
        printf("number or id name : %s\n",expVar) ;
        printf("value %s\n",id->value) ;
        GenPcode(g, "", "=", expVar, "", id->value);                            //  中間碼:例如 = 0 sum     
        HashTablePut(g->symTable, id->value, id->value);                        //  將 id 加入到符號表中                              
        strcpy(rzVar, expVar);                                                  //  傳回 EXP 的變數,例如 T0  
      }
可以看到我們的程式又往下一層去找了
Tree *exp = node->childs->item[2];
這時候就是代表 我們 tree index = 2
+PROG
 +BaseList
  +BASE
   +STMT
     idx=0, token=sum, type=id
     idx=1, token==, type==
    +EXP
      idx=2, token=0, type=number       <<< 就是我
那他又把 GenCode(g, exp, expVar); 我們這棵額外的 tree 又丟給自己那他下一次會跑去哪裡咧
可以知道他下一次走的節點是 EXP
 else if (strPartOf(node->type, "|EXP|")) {                                  // 處理 EXP
    // 處理運算式 EXP = ITEM ([+-*/] ITEM)*         
    
    Tree *item1 = node->childs->item[0];                                        // 取得子節點            
    char var1[100], var2[100], tempVar[100];        
    printf("EXP :%s\n" ,item1->value);         
    printf("EXP type:%s\n" ,item1->type);                                                 
    GenCode(g, item1, var1);                                                    // 遞迴產生 ITEM
    if (node->childs->count > 1) {
      Tree* op = node->childs->item[1];                                        // 連續取得 (op ITEM)? 
      Tree* item2 = node->childs->item[2];                                                         
      GenCode(g, item2, var2);                                                  // 遞迴產生 ITEM        
      GenTempVar(g, tempVar);                                                   // 取得臨時變數,例如T0  
      GenPcode(g, "", op->value, var1, var2, tempVar);                          // 中間碼:例如 + sum i T0
      strcpy(var1, tempVar);                                                    // 傳回臨時變數,例如 T0  
    }                                                                                                 
    
    strcpy(rzVar, var1);                                                        // 傳回臨時變數,例如 T0  
  } 
那麼這邊又做了什麼事
第一我們的樹其實是從 上一層傳遞過來的所以是子節點
 Tree *item1 = node->childs->item[0];   
這一行他直接 上一層的 子節點 在遞迴一次 也就是下面這個狀態
GenCode(g, item1, var1);  
=====PCODE=====
EXP :0
EXP type:number
number or id :0
number or id name : 0
value sum
         =    0         sum
所以最後他還是跟前幾個分析的程式依樣判斷是否合法
然後真的合法的 變數名稱就 這層 tree 的 值給返回
number or id :0
number or id name : 0
 else if (strPartOf(node->type, "|number|id|")) {                            // 處理 number, id 節點
    // 遇到變數或常數,傳回其 value 名稱。                                  
        printf("number or id :%s\n",node->value) ;                        
    strcpy(rzVar, node->value);                                                 // 直接傳回 id 或 number
  }
可以看到他是直接
strcpy(rzVar, node->value); 取得值往上一層丟 “|number|id|”
strcpy(rzVar, var1); 取得值往上一層丟 “|EXP|”
strcpy(rzVar, expVar); 取得值往上一層丟 “|STMT|”
所以到這邊就可以攔截的到值
      if (strEqual(op->type, "=")) {                                            // 處理 id= EXP             
        // STMT 是 id '=' EXP                                                   //  取得子節點               
        Tree *exp = node->childs->item[2];                                                                
        char expVar[100];          
        GenCode(g, exp, expVar);                                                //  遞迴產生 EXP            
        printf("number or id name : %s\n",expVar) ;
        printf("value %s\n",id->value) ;
往下面看

中間碼 P-CODE

GenPcode

這邊快速帶過因為我們已經知道我們傳過來的值是
=====PCODE=====
...
number or id :0
number or id name : 0
value sum
--------------------------------下面才是真正的中間碼
         =    0         sum
        GenPcode(g, "", "=", expVar, "", id->value);                            //  中間碼:例如 = 0 sum     

產生組合語言 Asm Generator

那麼 這邊要來轉為 ASM 就有
void GenPcode(Generator *g, char* label, char* op, char* p1, char* p2, char* pTo) {        // 輸出pcode後再轉為組合語言        
  char labelTemp[100];                                                                                                      
  if (strlen(label)>0)                                                                                                      
    sprintf(labelTemp, "%s:", label);                                                                                       
  else                                                                                                                      
    strcpy(labelTemp, "");                                                                                                  
  printf("%-8s %-4s %-4s %-4s %-4s\n", labelTemp, op, p1, p2, pTo);                        // 印出 pcode                       
  GenPcodeToAsm(g, labelTemp, op, p1, p2, pTo);                                            // 將 pcode 轉為組合語言                                 
}         
值照塞
void GenPcodeToAsm(Generator *g, char* label, char* op, char* p1, char* p2, char* pTo) {   // 將 pcode 轉為組合語言的函數
  if (strlen(label)>0)                                                                     // 如果有標記                                       
    GenAsmCode(g, label, "", "", "", "");                                                  //  輸出標記                          
  if (strEqual(op, "=")) { // pTo = p1                                                     // 處理等號 (= 0 sum)                
    GenAsmCode(g, "", "LD", "R1", p1, "");                                                 // 轉成 LDI R1, 0                   
    GenAsmCode(g, "", "ST", "R1", pTo, "");                                                //      ST R1, sum                  
  } else if (strPartOf(op, "|+|-|*|/|")) { // pTo = p1 op p2                               // 處理運算(+ sum i sum)            
    char asmOp[100];                                                                                                        
    if (strEqual(op, "+")) strcpy(asmOp, "ADD");                                           // 根據 op 設定運算指令             
    else if (strEqual(op, "-")) strcpy(asmOp, "SUB");                                                                       
    else if (strEqual(op, "*")) strcpy(asmOp, "MUL");                                                                       
    else if (strEqual(op, "/")) strcpy(asmOp, "DIV");                                                                       
    GenAsmCode(g, "", "LD", "R1", p1, "");                                                 // 轉成 LD R1, sum                  
    GenAsmCode(g, "", "LD", "R2", p2, "");                                                 //      LD R2, i                    
    GenAsmCode(g, "", asmOp,"R3", "R1", "R2");                                             //      ADD R3, R1, R2              
    GenAsmCode(g, "", "ST", "R3", pTo, "");                                                //      ST R3, sum                  
  } else if (strEqual(op, "CMP")) { // CMP p1, p2                                          // 處理 CMP (cmp i 10)              
    GenAsmCode(g, "", "LD", "R1", p1, "");                                                 // 轉成 LD R1, i                    
    GenAsmCode(g, "", "LD", "R2", p2, "");                                                 //      LDI R2, 10                  
    GenAsmCode(g, "", "CMP", "R1", "R2", "");                                              //      CMP R1, R2                  
  } else if (strEqual(op, "J")) { // J op label                                            // 處理 J (J > _FOR)                
    char asmOp[100];                                                                       // 根據 op 設定跳躍指令             
    if (strEqual(p1, "=")) strcpy(asmOp, "JEQ");                                                   
    else if (strEqual(p1, "!=")) strcpy(asmOp, "JNE");                                                                      
    else if (strEqual(p1, "<")) strcpy(asmOp, "JLT");                                                                       
    else if (strEqual(p1, ">")) strcpy(asmOp, "JGT");                                                                       
    else if (strEqual(p1, "<=")) strcpy(asmOp, "JLE");                                                                      
    else if (strEqual(p1, ">=")) strcpy(asmOp, "JGE");                                                                      
    else strcpy(asmOp, "JMP");                                                                                              
    GenAsmCode(g, "", asmOp, pTo, "", "");                                                                                   
  } else if (strEqual(op, "RET")) {                                                        // 處理 RET sum                     
    GenAsmCode(g, "", "LD", "R1", pTo, "");                                                // 轉成 LD R1, sum                  
    GenAsmCode(g, "", "RET", "", "", "");                                                  //      RET                         
  }                                                                                                         
}                                                                                                  
           
對印到我們的一個CASE
         
  if (strEqual(op, "=")) { // pTo = p1                                                     // 處理等號 (= 0 sum)                
    GenAsmCode(g, "", "LD", "R1", p1, "");                                                 // 轉成 LDI R1, 0                   
    GenAsmCode(g, "", "ST", "R1", pTo, "");                                                //      ST R1, sum                  
寫入 ASM FILE
void GenAsmCode(Generator *g, char* label, char* op, char* p1, char* p2, char* pTo) {      // 輸出組合語言指令                                     
  char *realOp = op;                                                                                                                            
  if (strEqual(op, "LD"))                                                                                                   
    if (isdigit(p2[0]))                                                                    // 如果指令是 LD,而且              
      realOp = "LDI";                                                                      //   p2 為常數                      
  fprintf(g->asmFile, "%-8s %-4s %-4s %-4s %-4s\n", label, realOp, p1, p2, pTo);           //    改用 LDI 指令                 
}     

最終OUTPUT

=====AsmFile:test2.asmmake======
         LDI  R1   0
         ST   R1   sum
         LDI  R1   0
         ST   R1   i
FOR0:
         LD   R1   i
         LDI  R2   10
         CMP  R1   R2
         JGT  _FOR0
         LD   R1   sum
         LD   R2   i
         ADD  R3   R1   R2
         ST   R3   T0
         LD   R1   T0
         ST   R1   sum
         LD   R1   i
         LDI  R2   1
         ADD  R3   R1   R2
         ST   R3   i
         JMP  FOR0
_FOR0:
         LD   R1   sum
         RET
sum:     RESW 1
i:       RESW 1
T0:      RESW 1
今天又要來分析虛擬機,小例子FOR 就不多做分析囉 ,以後買書再來補足
GOOGLE 搜尋規則要改了!??? 工程師生態直接大改變 (希望不會。