我個人的學習經驗是先去找一個你用過的語言,例如C++或是Go之類的
然後找由它們實作的編譯器原始碼觀察,大致應該都能找到 lexer parser codegen(or execute-enigne)這些部份
接著找本基礎書籍來看(我推薦龍書或是Parr的程式語言實作模式)
一一對照實際程式跟理論怎麼接合的,一般來說在書裡通常只找得到殘缺的實際案例
所以找到一個優秀的實作非常重要,我認為Go本身的原始碼就非常值得閱讀
另外也有一些比較簡單的實作(如果你害怕一個大規模的編譯器很難追蹤跟看懂)
例如用Go實作的gisp,用Rust的dyon
以上專案都值得看過
Lexer的部份,我認為實作BrainFuck是非常適合的練習,我也有用Nim做了一個
因為狀態比較少,適合你專注於整體概念上,而不是被複雜的細節繞到暈頭轉向
Parser的部份我認為還是手刻比較方便,並不是說我不用工具,事實上我通常用工具
不過你是要學習,我認為工具對這階段來說並沒有意義
這部份可以嘗試寫出四則運算式的解析器
func parseDefine() *VarDefine {
name := lex.Get()
if name.Typ == ID {
if lex.Get().Typ == ASSIGN {
return &VarDefine{
name: name,
expr: parseExpr(),
}
}
}
}
上面的函式可以觀察出如何透過遞迴調用產生AST
AST? 我聽到你的疑問了
所謂AST就是抽象語法樹的意思,透過複數的AST互相包含(上述的變數宣告就包含運算式)抑或連貫(變數宣告就是連貫的)成為一支原始程式的中間表述法,進而簡化翻譯為目標語言的難度
因為我們可以宣告方法處理AST資料結構,然後迭代處理整個AST樹
例如函數的AST可能可以這樣定義
type FunctionAst struct {
name string
functionType TypeAst
statements StatementsAst
}
func (f *Function) Codegen(module Module) {
f := createFunction(module, name, functionType.Codegen(module))
block := statements.Codegen(module)
Insert(f, block)
}
可能會像這樣,不斷深入下一層的AST,直到完成翻譯工作
至於優化工作,那又是新的篇章了,期待下次介紹
沒有留言:
張貼留言