InnerC 用於 ILBC, 我如今把它獨立一個版本出來, 項目地址:html
https://github.com/kelin-xycs/InnerC ,git
InnerC 是一個 C 語言 編譯器, 最初的 目的 是 做爲 ILBC 的 中間語言 編譯器 用於編譯 C 中間語言 。github
有關 ILBC , 見 《ILBC 規範》 http://www.javashuo.com/article/p-hsmtjoox-s.html 。正則表達式
目前 InnerC 已實現的部分 只包含 語法分析 和 語法檢查, 不包含 生成目標代碼 和 連接 。數組
目前 InnerC 支持 全局變量 函數 結構體 數組 指針 函數指針, int float char , 四則運算, 大於小於不等於 比較, 與或非邏輯運算,架構
if 語句, while 語句, 不支持 for 語句, 主要是 懶得寫了,煩 。 之後能夠加上 。函數
支持 return break continue 語句 。學習
支持 做用域, 好比 函數體 是一個 做用域, 函數形參 是一個 做用域, if 子句 和 while 子句(循環體) 是 一個 做用域 。測試
不支持 ++ -- += -=, 也是 沒時間寫 。 之後能夠加上 。spa
不支持 三元運算符 ? : , 三元運算符 的 規則 和 通常的 運算符 有所不一樣, 要 額外 的 一些 語法分析 邏輯 來 處理 。 之後能夠加上 。
語法檢查 的 部分 只 粗略 的 實現了 檢查 變量是否已聲明, 是否在 上級做用域 中聲明瞭同名的變量, 只寫了代碼,沒有測試 。
另外還實現了 函數名 和 結構體名 的 命名檢查, 就是 應該由 下劃線字母數字 組成 且 以 下劃線字母 開頭,以及 不能 和 關鍵字 相同 。
命名檢查 和 語法檢查 是 分開的,由於 在 語法檢查 裏 檢查 函數名 和 結構體名 是否存在, 因此 先 進行 命名檢查 。
目前 命名檢查 包含在 文本解析(Parse)過程 中 。
大部分 的 語法檢查 都在 I_C_Member.類型和語法檢查() 方法 裏 實現 。
只要 去 實現 I_C_Member 接口 的 類型和語法檢查() 方法 就好了 。
全部的 語法成員 都 繼承了 I_C_Member 接口 , 包括 變量聲明 結構體 函數 做用域 各類語句 各類表達式 。
因此這個架構是 很清晰 的, 完善剩餘的部分 只是 工做量 的 問題 。
這裏把 類型和語法檢查 要作 的 工做 大概 列一下 :
檢查上級做用域中是否已定義了同名的變量
變量 參數 返回值 字段 的 類型 是否正確,好比 是不是 int float 等基礎類型或結構體
是否使用了 未定義 的 變量 參數 字段
變量不能在聲明前使用
運算符兩邊的表達式的類型是否匹配
Cast 是否合法
函數返回值的類型和聲明的返回類型是否一致
是否使用了 未定義 的 函數 和 結構體
數組聲明 的 維度長度 只能是 常量 或者 常量表達式,若是用 常量 初始化數組,能夠不用聲明維度長度,但這好像只適用於 一維數組
全局變量 初始化 只能用 常量 或者 常量表達式
由於 大部分 的 語法檢查 都和 類型 有關,因此歸到一塊兒稱爲 「類型和語法檢查」
這些內容 在 代碼 的 註釋 裏有寫 。
除了以上,還有 2 個 語法檢查 是 在 類型和語法檢查 以後 獨立 進行的,分別是 :
檢查函數內全部路徑都有返回值
檢查結構體不能循環包含
這個 流程 在 代碼 裏 能夠很清楚的 看到 。
能夠在 解決方案 中的 InnerC_Demo 項目 看到 Demo, 這是一個 WinForm 項目, 運行 InnerC_Demo.exe, 指定要編譯 的 C 源文件, 點擊 「測試」 按鈕, 若是沒有語法錯誤, 就會 把 C 源文件 編譯爲 語法成員樹, 並 將 語法成員樹 逆向 還原 爲 C 源代碼, 還原後 的 C 源代碼 保存在 另一個 文件裏, 這個文件的文件名 是 原文件名 加上 「.reverse.c」 , 好比 源文件名 是 「a.c」, 還原後的 文件名 是 「a.c.reverse.c」 。
在 InnerC_Demo 的 Bin\Debug 目錄下, 有一個 Test.c , 運行 InnerC_Demo.exe 能夠 編譯 Test.c 觀察 演示效果 。
此次對 C 語法 有一點 修改,就是 C 語言 是用 大括號 如 { 1, 2, 3, 4 } 表示 一個 數組常量, 可是這讓 InnerC 的 編譯器 變得複雜 。
由於 大括號 是用來表示 一個 代碼塊,好比 函數體, 結構體, 或者 if 子句, 或者 while 子句(循環體),
用 大括號 表示 數組常量 會讓 第一層解析 劃分 函數 和 結構體 的 大括號塊 變得 麻煩 。
爲了 維持 編譯器 的 簡單清晰, 我決定 作出一個 改革,
改用 中括號 來表示 數組常量,如 [ 1, 2, 3, 4 ] , 結果很爽 。 啊哈哈哈 。
我以爲 發明 C 語言 的 前輩 可能有 大括號 偏心癖好 , 要不就是 可能 看到 當時 其它語言 裏 用 中括號 表示 數組 以爲 不爽 。
將來 D# 也會沿用 這個 作法, D# 的 編譯器 能夠在 InnerC 的 基礎上 擴展而來 。
在 D# 中 有 Lambda 表達式, 這樣 是否是 仍然 要 增長 對 Lambda 表達式 的 判斷?
是的, 可是, Lambda 表達式 是一個 明顯的 主要的 需求, 並且 能夠根據 大括號 前面 是否有 ()=> 操做符 來 明確的 判斷 大括號 是不是 Lambda 表達式,
在 劃分 方法 的 大括號塊 時 加入 是不是 Lambda 表達式 大括號 的 判斷 不會讓 編譯器 架構 的 關注點 分散,
而 數組常量 是一個 很弱 的 需求, 在 劃分 函數 結構體 大括號塊 時 加入 是不是 數組常量 大括號 的 判斷 會讓 編譯器 架構 的 關注點 分散 。
咦? 你們可能會問, 在 函數 和 結構體 外 哪裏來的 數組常量? 全局變量 啊, 全局變量 的 初始化 可能會用 這種 大括號數組常量 。
若是 是 在 函數 和 結構體 內部, 其實 沒什麼問題 。
InnerC 還有另一個 意義, 就是 能夠做爲 編譯器 的 範例 和 內核, 讓後人能夠容易的 學習瞭解 編譯器 以及 在 這個基礎 上 改寫 和 開發 新的 編譯器 。
另外, 根據 InnerC 的原理, 其實 能夠寫一個 正則表達式 引擎 。 正則表達式 自己 就是一個 描述規則的文本,須要 文本解析, 解析獲得 規則, 把 規則 保存在 字典(Hash 表) 裏, 根據 規則 對 目標字符串 進行 匹配 。
這些用 InnerC 的 文本解析 方法 均可以實現 。