上面咱们来让计算器程序反对变量的应用,使得程序能够设置和获取变量的值。
从当初开始我将不掩藏咱们要实现的是一个程序语言,因为出自计算器
所以命名为 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.}
本来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
构造。
定义赋值节点的运行办法
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")
这里咱们须要增加变量名规定、赋值符号规定以及减少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 # 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语句》,欢送关注。