上面咱们来让计算器程序反对变量的应用,使得程序能够设置和获取变量的值。
从当初开始我将不掩藏咱们要实现的是一个程序语言,因为出自计算器
所以命名为 bkcalclang

这次的代码以上一篇《使计算器反对语句块》
的代码为根底编写,如果发现不相熟当下的内容能够回顾一下之前的篇章。

代码清单【go语言为例】

package mainimport (    "fmt"    "strconv"    "io/ioutil"    "./bklexer")var ValueDict map[string]float64type 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

定义命名节点的运行办法

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构造。

定义赋值节点的运行办法

func (assign *Assign) Eval() float64 {    value := assign.value.Eval()    ValueDict[assign.name] = value    return value}

该办法在执行时会将成员value的执行后果存入到ValueDict中而后返回该值。

定义输入节点的构造

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的值而后将其打印输出,最初返回该值。

减少一个函数用于专门解决语句

因为咱们应用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 # plusecho 3 - 4# here is a commentecho 5 * 6 # mulecho 7 / 8echo 1 + (2 - 3) * 4 / 5 # compositeset pi = 3.14set r = 5echo 2 * pi * r * r

运行后果:

➜ go run calc.go := 3:= -1:= 30:= 0.875:= 0.19999999999999996:= 157

下篇《反对If语句》,欢送关注。