MYC編譯器源碼之詞法分析

詞法解析

詞法解析的工作都由Tok類處理,其構(gòu)造函數(shù)接受一個Io對象做文件處理,下面是Tok構(gòu)造函數(shù)的源碼:

public Tok(Io ihandle)
{
    io = ihandle;
    // 初始化Token(字符歸類)字典
    InitHash();         // initialize the tokens hashtable
    // 讀入文件的第一個字符
    io.ReadChar();
    // 逐個掃描文件里的字符,獲取
    // 第一個字符歸類(Token)
    scan();
}

構(gòu)造函數(shù)中第一個函數(shù)調(diào)用InitHash的目的是將關(guān)鍵字和操作符解析成更容易識別的字符類型識別號 - Token,這樣做的目的是為了便于語法解析器parser處理。例如,對于下面這條C語句:

int foo(int a)

與其讓語法解析器去逐個處理單個字符,詞法解析器的作用是將去上面一行語句歸類成類似下面的格式:

T_INT T_IDENT ‘(‘ T_INT T_IDENT ‘)’

因為T_INT,T_IDENT都是一個整數(shù)型常量,而’(‘這樣的單個字符也可以當(dāng)作整數(shù)型常量對待,這樣語法解析器在分析語法的時候工作會更輕松些。所以在InitHash函數(shù)里,其把編程語言里所有的關(guān)鍵字和多字符操作符(如左移賦值操作符 <<=)都設(shè)置了類型標(biāo)識號(Token),在Tok對象的scan()函數(shù)掃描源文件時,會逐一在這個字典里查詢關(guān)鍵字的標(biāo)識號:

public void InitHash()
{
    // 為字符類型識別號對照表 – tokens分配空間
    tokens = new Hashtable();
    AddTok(T_LEFT_ASSIGN,   "<<=");     
    // ... ...
    AddTok(T_IF,        "if");
    // ... ...
    AddTok(T_STATIC,        "static");
    AddTok(T_INT,       "int");
    // ... ...
}

而對應(yīng)的每個標(biāo)識號(Token)的定義,則可以在Tok.cs源文件的最上面找到:

public const int T_LEFT_ASSIGN  = 10001;
// ... ...
public const int T_IF           = 20001;
// ... ...
public const int T_STATIC       = 30002;
// ... ...
public const int T_INT      = 40003;
// ... ...
public const int T_IDENT        = 50001;
public const int T_DIGITS       = 50002;
public const int T_UNKNOWN      = 99999;
public const int T_EOF      = -1;

字符類型識別號對照表初始化完畢后,語法分析器就可以調(diào)用Tok對象的scan函數(shù)進(jìn)行語法處理了,scan函數(shù)每次只處理并返回一個字符類型:

public void scan()
{
    // 跳過注釋、換行符、空格等字符
    skipWhite();
    // 先判斷當(dāng)前讀取的字符是不是一個字母
    // 如果是字母開頭的話,要么是關(guān)鍵字,
    // 要么就是變量名
    if (Char.IsLetter(io.getNextChar()))
      // 逐個掃描后面的字符,直到識別出關(guān)鍵字
      // 或者變量名為止才退出
      LoadName();
    // 如果當(dāng)前的字符是 0 - 9的數(shù)字
    else if (Char.IsDigit(io.getNextChar()))
      // 掃描完后面的數(shù)字并歸類
      LoadNum();
    // 如果是操作符,掃描完后面的操作符字符串
    else if (isOp(io.getNextChar()))
      LoadOp();
    // 如果文件已經(jīng)讀取完畢了
    else if (io.EOF())
      {
      // 返回特殊的識別符 T_EOF,表示文件讀取完畢
      value = null;
      token_id = T_EOF;
      }
    else
      {
      // 這個字符不是一個合法的字符,歸類成T_UNKNOWN
      // T_UNKNOWN沒有被任何語法引用
      // 如果語法分析器在掃描語法的過程中
      // 看到這個識別符,很有可能是源碼里有語法錯誤
      value = new StringBuilder(MyC.MAXSTR);
      value.Append(io.getNextChar());
      token_id = T_UNKNOWN;
      io.ReadChar();
      }
    skipWhite();
    // 條件編譯,如果是myc.exe是調(diào)試版本,則在命令行里
    // 打印出當(dāng)前識別的字符類型,便于myc.exe的開發(fā)者排錯
#if DEBUG
    Console.WriteLine("[tok.scan tok=["+this+"]");
#endif
}

scan函數(shù)是Tok對象里最核心的函數(shù),它實際上是完成前面myc語法里這些詞法規(guī)則(還有隱含的關(guān)鍵字和操作符識別):

letter ::= "A-Za-z";
digit ::= "0-9";

name ::= letter { letter | digit };
integer ::= digit { digit };

我們再通過說明LoadName函數(shù)來解釋詞法分析的細(xì)節(jié):

void LoadName()
{
  // 緩存讀取到的字符
  value = new StringBuilder(MyC.MAXSTR);
  skipWhite();  
  // 錯誤驗證 - 確保第一個字符是字母
  if (!Char.IsLetter(io.getNextChar()))
    throw new ApplicationException("?Expected Name");
  // 后面跟著的字符只能是數(shù)字或者字母
  while (Char.IsLetterOrDigit(io.getNextChar()))
    {
    // 緩存字符,以便判斷是變量名,還是關(guān)鍵字
    value.Append(io.getNextChar());
    // 從源文件里讀取下一個字符
    io.ReadChar();
    }
  // 在字符類型識別表里查詢讀取到的詞組是不是關(guān)鍵字
  token_id = lookup_id();
  // 不是關(guān)鍵字的話,那么就是變量名(或函數(shù)名)
  if (token_id <= 0)
    token_id = T_IDENT;
  skipWhite();
}

上面基本上就是詞法分析的關(guān)鍵代碼了,不過在說明的時候,我特意跳過了構(gòu)造函數(shù)的 io.ReadChar()這個函數(shù),這個函數(shù)從字面意義上看是讀取一個字符,但實際上從源文件一個字符一個字符的讀取效率實在是太低了,因此一般都是從源文件里讀取一大段字符并緩存在內(nèi)存里,提高效率:

// Io.cs – ReadChar函數(shù)

public void ReadChar()
{
  // 判斷是不是讀到文件末尾了
  if (_eof)         // if already eof, nothing to do here
    return;
  // 如果緩存還沒有實例化,或者緩存里的字符
  // 已經(jīng)處理完畢了,創(chuàng)建一個新的緩存
  // 對于老的緩存數(shù)組,丟給垃圾回收機(jī)制處理
  if (ibuf == null || ibufidx >= MyC.MAXBUF)
    {
    ibuf = new char[MyC.MAXBUF];
    _eof = false;
    // 從源文件里讀取一大塊內(nèi)容到緩存里
    ibufread = rfile.Read(ibuf, 0, MyC.MAXBUF);
    ibufidx = 0;
    if (buf == null)
      buf = new StringBuilder(MyC.MAXSTR);
    }
  // 從緩存里讀取下一個字符
  look = ibuf[ibufidx++];
  // 判斷這次讀取時,是否已經(jīng)到源文件末尾了
  if (ibufread < MyC.MAXBUF && ibufidx > ibufread)
    _eof = true;

  /*
   * track the read characters
   */
  // 保存當(dāng)前讀取的字符,以便在生成IL源文件的時候
  // 可以把C源碼跟生成的IL源碼對應(yīng)起來
  buf.Append(look);
  // 如果碰到換行,更新行號,行號在報告語法錯誤
  // 的時候會用到,告知具體語法出錯的行號便于
  // 程序員找到錯誤
  if (look == '\n')
    bufline++;
}

在Io.ReadChar函數(shù)里,會保存讀取的C源碼,當(dāng)要生成IL源文件的時候,這個信息用來保存C語句跟IL語句的對應(yīng)關(guān)系,如用下面的命令編譯myc里自帶的測試源碼文件:

myc-list-cmd.png

效果如下圖:

myc-list-result.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文涉及的javac編譯器來自openjdk. javac的目錄地址為: 解壓目錄/langtools/src/s...
    whthomas閱讀 1,543評論 3 3
  • 車無疑是夢的翅膀,自由更是夢的天堂! 我的車?yán)餂]有美女,只有酒!我酒量不大,里邊的酒絕大部分是給別人準(zhǔn)備的。我喜歡...
    英馳商貿(mào)閱讀 456評論 0 1
  • 目錄 葉姍姍說過讓我不要去看她,可是這幾天我越想越覺得愧疚,拋開別的不說,全靠她心甘情愿把事情經(jīng)過說出來,并且把布...
    疏牧風(fēng)閱讀 480評論 0 1
  • 風(fēng)和日麗的一天,冬娜打扮了三個小時才出門,出門前照著鏡子看了很久,像被什么勾了魂兒似的。出門后她沒有按照計劃的方向...
    彼岸久伴閱讀 296評論 0 0
  • github 新賬號: ziran655@126.com 密碼: ziran4082227 第二個郵箱: olif...
    olifer閱讀 200評論 0 0

友情鏈接更多精彩內(nèi)容