使程序語言支持變量

下面咱們來讓計算器程序支持變量的使用,使得程序能夠設置和獲取變量的值。
從如今開始我將不掩藏咱們要實現的是一個程序語言,由於出自計算器
因此命名爲 bkcalclangbash

此次的代碼以上一篇《使計算器支持語句塊》
的代碼爲基礎編寫,若是發現不熟悉當下的內容能夠回顧一下以前的篇章。app

代碼清單【go語言爲例】

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並實例化Namepost

定義命名節點的運行方法

func (name *Name) Eval() float64 {
    if value, found := ValueDict[name.name]; found {
        return value;
    }
    return 0.
}

本來NodeGetValue方法更名爲Eval,這一點一樣做用於其它相關結構體,須要注意。
NameEval方法會查找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")

這裏咱們須要添加變量名規則、賦值符號規則以及增長setecho這兩個保留字。

讀取文件進行解析計算

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語句》,歡迎關注。

相關文章
相關標籤/搜索