下面咱們來讓計算器程序支持變量的使用,使得程序能夠設置和獲取變量的值。
從如今開始我將不掩藏咱們要實現的是一個程序語言,由於出自計算器
因此命名爲 bkcalclangbash
此次的代碼以上一篇《使計算器支持語句塊》
的代碼爲基礎編寫,若是發現不熟悉當下的內容能夠回顧一下以前的篇章。app
package main import ( "fmt" "strconv" "io/ioutil" "./bklexer" ) var ValueDict map[string]float64 type Node interface { Eval() float64 } type Block struct { statements []Node } func NewBlock() *Block { return &Block{} } func (block *Block) AddStatement(statement Node) { block.statements = append(block.statements, statement) } func (block *Block) Eval() { for _, statement := range block.statements { statement.Eval() } } type Number struct { value float64 } func NewNumber(token *BKLexer.Token) *Number { value, _ := strconv.ParseFloat(token.Source, 64) return &Number{value: value} } func (number *Number) Eval() float64 { return number.value } type Name struct { name string } func NewName(token *BKLexer.Token) *Name { return &Name{name: token.Source} } func (name *Name) Eval() float64 { if value, found := ValueDict[name.name]; found { return value; } return 0. } type BinaryOpt struct { opt string lhs Node rhs Node } func NewBinaryOpt(token *BKLexer.Token, lhs Node, rhs Node) *BinaryOpt { return &BinaryOpt{opt: token.Source, lhs: lhs, rhs: rhs} } func (binaryOpt *BinaryOpt) Eval() float64 { lhs, rhs := binaryOpt.lhs, binaryOpt.rhs switch binaryOpt.opt { case "+": return lhs.Eval() + rhs.Eval() case "-": return lhs.Eval() - rhs.Eval() case "*": return lhs.Eval() * rhs.Eval() case "/": return lhs.Eval() / rhs.Eval() } return 0 } type Assign struct { name string value Node } func NewAssign(token *BKLexer.Token, value Node) *Assign { return &Assign{name: token.Source, value: value} } func (assign *Assign) Eval() float64 { value := assign.value.Eval() ValueDict[assign.name] = value return value } type Echo struct { value Node } func NewEcho(value Node) *Echo { return &Echo{value: value} } func (echo *Echo) Eval() float64 { value := echo.value.Eval() fmt.Println(":=", value) return value } func parse(lexer *BKLexer.Lexer) *Block { block := NewBlock() token := lexer.NextToken() for token.TType == BKLexer.TOKEN_TYPE_NEWLINE { token = lexer.NextToken() } for token.TType != BKLexer.TOKEN_TYPE_EOF { statement := parse_statement(lexer) if statement == nil { return nil; } token = lexer.GetToken() if token.TType != BKLexer.TOKEN_TYPE_NEWLINE && token.TType != BKLexer.TOKEN_TYPE_EOF { return nil; } block.AddStatement(statement) for token.TType == BKLexer.TOKEN_TYPE_NEWLINE { token = lexer.NextToken() } } return block } func parse_statement(lexer *BKLexer.Lexer) Node { token := lexer.GetToken() if token.Name == "SET" { name := lexer.NextToken() if name.Name != "NAME" { return nil } token = lexer.NextToken() if token.Name != "ASSIGN" { return nil } lexer.NextToken() value := parse_binary_add(lexer) if value == nil { return nil } return NewAssign(name, value) } else if token.Name == "ECHO" { lexer.NextToken() value := parse_binary_add(lexer) if (value == nil) { return nil } return NewEcho(value) } return parse_binary_add(lexer) } func parse_binary_add(lexer *BKLexer.Lexer) Node { lhs := parse_binary_mul(lexer) if lhs == nil { return nil } token := lexer.GetToken() for token.Source == "+" || token.Source == "-" { lexer.NextToken() rhs := parse_binary_mul(lexer) if rhs == nil { return nil } lhs = NewBinaryOpt(token, lhs, rhs) token = lexer.GetToken() } return lhs } func parse_binary_mul(lexer *BKLexer.Lexer) Node { lhs := factor(lexer) if lhs == nil { return nil } token := lexer.GetToken() for token.Source == "*" || token.Source == "/" { lexer.NextToken() rhs := factor(lexer) if rhs == nil { return nil } lhs = NewBinaryOpt(token, lhs, rhs) token = lexer.GetToken() } return lhs } func factor(lexer *BKLexer.Lexer) Node { token := lexer.GetToken() if token.Name == "LPAR" { lexer.NextToken() expr := parse_binary_add(lexer) if expr == nil { return nil } token := lexer.GetToken() if token.Name != "RPAR" { return nil } lexer.NextToken() return expr } if token.Name == "NUMBER" { number := NewNumber(token) lexer.NextToken() return number } if token.Name == "NAME" { name := NewName(token) lexer.NextToken() return name } return nil } func main() { lexer := BKLexer.NewLexer() lexer.AddRule("\\d+\\.?\\d*", "NUMBER") lexer.AddRule("[\\p{L}\\d_]+", "NAME") lexer.AddRule("\\+", "PLUS") lexer.AddRule("-", "MINUS") lexer.AddRule("\\*", "MUL") lexer.AddRule("/", "DIV") lexer.AddRule("\\(", "LPAR") lexer.AddRule("\\)", "RPAR") lexer.AddRule("=", "ASSIGN") lexer.AddIgnores("[ \\f\\t]+") lexer.AddIgnores("#[^\\r\\n]*") lexer.AddReserve("set") lexer.AddReserve("echo") bytes, err := ioutil.ReadFile("../test.txt") if err != nil { fmt.Println("read faild") return } code := string(bytes) lexer.Build(code) result := parse(lexer) if result == nil { fmt.Println("null result") return } ValueDict = make(map[string]float64) result.Eval() }
import ( "fmt" "strconv" "io/ioutil" "./bklexer" )
fmt
打印輸出strconv
字符串轉換io/ioutil
讀取文件./bklexer
用於詞法解析var ValueDict map[string]float64
咱們會使用一個map
類型的對象來存取值,並以此實現變量賦值和取值的操做。函數
type Name struct { name string } func NewName(token *BKLexer.Token) *Name { return &Name{name: token.Source} }
Name
結構體用於變量取值相關操做,函數NewName
接收參數*BKLexer.Token
並實例化Name
。post
func (name *Name) Eval() float64 { if value, found := ValueDict[name.name]; found { return value; } return 0. }
本來Node
的GetValue
方法更名爲Eval
,這一點一樣做用於其它相關結構體,須要注意。Name
的Eval
方法會查找ValueDict
中的對應值並返回,若是不存在則返回0。測試
type Assign struct { name string value Node } func NewAssign(token *BKLexer.Token, value Node) *Assign { return &Assign{name: token.Source, value: value} }
定義Assign
結構用於存放賦值語句信息,name
爲變量名,value
爲對應值的節點結構。
使用NewAssign
函數能夠實例化Assign
結構。ui
func (assign *Assign) Eval() float64 { value := assign.value.Eval() ValueDict[assign.name] = value return value }
該方法在執行時會將成員value
的執行結果存入到ValueDict
中而後返回該值。code
type Echo struct { value Node } func NewEcho(value Node) *Echo { return &Echo{value: value} }
Echo
結構存儲一個類型爲Node
的成員value
,咱們使用NewEcho
實例化它。對象
func (echo *Echo) Eval() float64 { value := echo.value.Eval() fmt.Println(":=", value) return value }
在該方法中,咱們先取得echo
成員value
的值而後將其打印輸出,最後返回該值。token
因爲咱們使用parse_statement
函數做爲處理語句的函數,因此咱們在某些地方須要作出相應的修改,
如語法解析的入口parse
函數:字符串
for token.TType != BKLexer.TOKEN_TYPE_EOF { statement := parse_statement(lexer) if statement == nil { return nil; }
func parse_statement(lexer *BKLexer.Lexer) Node { token := lexer.GetToken() if token.Name == "SET" { name := lexer.NextToken() if name.Name != "NAME" { return nil } token = lexer.NextToken() if token.Name != "ASSIGN" { return nil } lexer.NextToken() value := parse_binary_add(lexer) if value == nil { return nil } return NewAssign(name, value) } else if token.Name == "ECHO" { lexer.NextToken() value := parse_binary_add(lexer) if (value == nil) { return nil } return NewEcho(value) } return parse_binary_add(lexer) }
若是發現起頭的token
名稱爲SET
則判斷爲賦值操做,取下一個token
做爲變量名,
再取下一個判斷是否爲賦值符號,若是都成功則解析後面的內容並以此構建賦值節點。
if token.Name == "SET" { name := lexer.NextToken() if name.Name != "NAME" { return nil } token = lexer.NextToken() if token.Name != "ASSIGN" { return nil } lexer.NextToken() value := parse_binary_add(lexer) if value == nil { return nil } return NewAssign(name, value)
若是當前token
名稱爲ECHO
則判斷爲打印輸出,須要跳過當前token
並進行表達式解析。
若是成功解析則用解析結果構建打印輸出節點並返回,不然函數返回nil
。
} else if token.Name == "ECHO" { lexer.NextToken() value := parse_binary_add(lexer) if (value == nil) { return nil } return NewEcho(value) }
將以前的parse_number
函數更名爲factor
【這並非必要操做】:
func factor(lexer *BKLexer.Lexer) Node {
咱們在factor
函數中添加變量名解析代碼:
if token.Name == "NAME" { name := NewName(token) lexer.NextToken() return name }
lexer.AddRule("\\d+\\.?\\d*", "NUMBER") lexer.AddRule("[\\p{L}\\d_]+", "NAME") lexer.AddRule("\\+", "PLUS") lexer.AddRule("-", "MINUS") lexer.AddRule("\\*", "MUL") lexer.AddRule("/", "DIV") lexer.AddRule("\\(", "LPAR") lexer.AddRule("\\)", "RPAR") lexer.AddRule("=", "ASSIGN") lexer.AddIgnores("[ \\f\\t]+") lexer.AddIgnores("#[^\\r\\n]*") lexer.AddReserve("set") lexer.AddReserve("echo")
這裏咱們須要添加變量名規則、賦值符號規則以及增長set
、echo
這兩個保留字。
bytes, err := ioutil.ReadFile("../test.txt") if err != nil { fmt.Println("read faild") return } code := string(bytes) lexer.Build(code) result := parse(lexer) if result == nil { fmt.Println("null result") return } ValueDict = make(map[string]float64) result.Eval()
須要注意,咱們在執行result.Eval()
以前必須先實例化ValueDict
。
測試內容:
echo 1 + 2 # plus echo 3 - 4 # here is a comment echo 5 * 6 # mul echo 7 / 8 echo 1 + (2 - 3) * 4 / 5 # composite set pi = 3.14 set r = 5 echo 2 * pi * r * r
運行結果:
➜ go run calc.go := 3 := -1 := 30 := 0.875 := 0.19999999999999996 := 157
下篇《支持If語句》,歡迎關注。