返回教程主页
上篇 使用BKLexer进行词法分析
利用递归向下算法结合我们的BKLexer
实现支持四则运算与括号优先级的计算器程序。
代码清单【Golang】
package main
import (
"fmt"
"os"
"bufio"
"strings"
"strconv"
"./bklexer"
)
type Node interface {
GetValue() float64
}
type Number struct {
value float64
}
func NewNumber(token *BKLexer.Token) *Number {
value, _ := strconv.ParseFloat(token.Source, 64)
return &Number{value: value}
}
func (number *Number) GetValue() float64 {
return number.value
}
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) GetValue() float64 {
lhs, rhs := binaryOpt.lhs, binaryOpt.rhs
switch binaryOpt.opt {
case "+": return lhs.GetValue() + rhs.GetValue()
case "-": return lhs.GetValue() - rhs.GetValue()
case "*": return lhs.GetValue() * rhs.GetValue()
case "/": return lhs.GetValue() / rhs.GetValue()
}
return 0
}
func parse(lexer *BKLexer.Lexer) Node {
result := parse_binary_add(lexer)
token := lexer.GetToken()
if token.TType != BKLexer.TOKEN_TYPE_EOF {
return nil
}
return result
}
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 == "-" {
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 := parse_number(lexer)
if lhs == nil {
return nil
}
token := lexer.GetToken()
for token.Source == "*" || token.Source == "/" {
rhs := parse_number(lexer)
if rhs == nil {
return nil
}
lhs = NewBinaryOpt(token, lhs, rhs)
token = lexer.GetToken()
}
return lhs
}
func parse_number(lexer *BKLexer.Lexer) Node {
token := lexer.NextToken()
if token.Name == "LPAR" {
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
}
return nil
}
func main() {
fmt.Println("Hello My Calc.")
lexer := BKLexer.NewLexer()
lexer.AddRule("\\d+\\.?\\d*", "NUMBER")
lexer.AddRule("\\+", "PLUS")
lexer.AddRule("-", "MINUS")
lexer.AddRule("\\*", "MUL")
lexer.AddRule("/", "DIV")
lexer.AddRule("\\(", "LPAR")
lexer.AddRule("\\)", "RPAR")
lexer.AddIgnores("[ \\f\\t]+")
reader := bufio.NewReader(os.Stdin)
for true {
fmt.Print("> ")
inputs, _ := reader.ReadString('\n')
inputs = strings.Trim(inputs, " \f\t\n")
if inputs == "quit" {
break
}
if inputs != "" {
lexer.Build(inputs)
result := parse(lexer)
if result == nil {
positon := lexer.GetToken().Col
fmt.Println("error in :", positon)
continue
}
fmt.Println("out =", result.GetValue())
}
}
fmt.Println("bye!")
}
运行测试
➜ go calc.go
Hello My Calc.
> 1 + (2 - 3) * 4 / 5
out = 0.19999999999999996
> quit
bye!
引入需要的包
import (
"fmt"
"os"
"bufio"
"strings"
"strconv"
"./bklexer"
)
- fmt 打印输出
- os + bufio 读取用户输入
- strings 处理字符串
- strconv 解析字符串
- bklexer 用于词法分析
定义接口
type Node interface {
GetValue() float64
}
Node
作为接口将用于指向其它的结构,其GetValue
方法可以获得节点的数值。
定义数字节点
type Number struct {
value float64
}
func NewNumber(token *BKLexer.Token) *Number {
value, _ := strconv.ParseFloat(token.Source, 64)
return &Number{value: value}
}
Number
作为数字类型的节点,有成员value
用于存储数值,使用NewNumber
函数可以实例它。
实现数字节点的接口方法
func (number *Number) GetValue() float64 {
return number.value
}
定义运算操作节点
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}
}
BinaryOpt
作为运算操作节点,成员opt
记录运算符号,lhs
、rhs
分别为表达式左、右两边的内容,如1+2中的1与2。使用NewBinaryOpt
函数实例它。
实现运算节点的接口方法
func (binaryOpt *BinaryOpt) GetValue() float64 {
lhs, rhs := binaryOpt.lhs, binaryOpt.rhs
switch binaryOpt.opt {
case "+": return lhs.GetValue() + rhs.GetValue()
case "-": return lhs.GetValue() - rhs.GetValue()
case "*": return lhs.GetValue() * rhs.GetValue()
case "/": return lhs.GetValue() / rhs.GetValue()
}
return 0
}
我们需要根据运算操作符号opt
判断lhs
与rhs
的值究竟以怎样的方式进行计算。
定义语法解析的入口函数
func parse(lexer *BKLexer.Lexer) Node {
result := parse_binary_add(lexer)
token := lexer.GetToken()
if token.TType != BKLexer.TOKEN_TYPE_EOF {
return nil
}
return result
}
入口函数parse
接收到*BKLexer.Lexer
类型的参数后,立即将其发送到parse_binary_add
中,尝试解析运算等级与加法操作相同的运算操作。最后判断当前token
是否为结尾,若不是则返回nil
否则返回解析结果。
定义加法等级运算操作的解析函数
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 == "-" {
rhs := parse_binary_mul(lexer)
if rhs == nil {
return nil
}
lhs = NewBinaryOpt(token, lhs, rhs)
token = lexer.GetToken()
}
return lhs
}
当函数接收到参数lexer
并运行时会先尝试解析乘法等级的运算操作,如果解析完毕并成功返回则判断当前token
的字面量决定是否需要构建加法等级运算节点,如果需要则尝试进行另一个乘法等级的运算操作解析,解析成功则根据opt
构建当前运算等级的节点对象。
定义乘法等级运算操作的解析函数
func parse_binary_mul(lexer *BKLexer.Lexer) Node {
lhs := parse_number(lexer)
if lhs == nil {
return nil
}
token := lexer.GetToken()
for token.Source == "*" || token.Source == "/" {
rhs := parse_number(lexer)
if rhs == nil {
return nil
}
lhs = NewBinaryOpt(token, lhs, rhs)
token = lexer.GetToken()
}
return lhs
}
parse_binary_mul
基本和parse_binary_add
一样,只是在运行开始是尝试解析数字或括号表达式的内容,在判断需要构建乘法等级运算节点的时候也是尝试解析另一个数字或括号表达式的内容。
定义数字与括号表达式的解析函数
func parse_number(lexer *BKLexer.Lexer) Node {
token := lexer.NextToken()
if token.Name == "LPAR" {
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
}
return nil
}
首先取得下一个token
,判断是否是左括号,如果是则可能为括号表达式,需要尝试进行解析,如果解析成功还需要判断结尾是否为右括号,全部成功则返回解析的表达式节点,否则返回nil
。若一开始的token
不是左括号而是数字则实例一个数字节点并返回。若既不是左括号又不是数字则返回nil
。
定义词法分析器规则
lexer := BKLexer.NewLexer()
lexer.AddRule("\\d+\\.?\\d*", "NUMBER")
lexer.AddRule("\\+", "PLUS")
lexer.AddRule("-", "MINUS")
lexer.AddRule("\\*", "MUL")
lexer.AddRule("/", "DIV")
lexer.AddRule("\\(", "LPAR")
lexer.AddRule("\\)", "RPAR")
lexer.AddIgnores("[ \\f\\t]+")
循环读取用户输入并解析计算
reader := bufio.NewReader(os.Stdin)
for true {
fmt.Print("> ")
inputs, _ := reader.ReadString('\n')
inputs = strings.Trim(inputs, " \f\t\n")
if inputs == "quit" {
break
}
if inputs != "" {
lexer.Build(inputs)
result := parse(lexer)
if result == nil {
positon := lexer.GetToken().Col
fmt.Println("error in :", positon)
continue
}
fmt.Println("out =", result.GetValue())
}
}
fmt.Println("bye!")
当解析函数parse
返回的内容不是nil
时可以使用GetValue
函数计算表达式并得到结果。
下篇 使计算器支持语句块