对go的喜欢已经超过了C/C ++ 😂,继续尝试着用go来完成一个包含有括号及加、减、乘、除的简易版计算器。并可以实现浮点计算。中间还利用到二叉树的后序遍历。
基本思路:
1、从终端输入表达式,并读取,先进行去除空格等操作;
2、将处理好的表达式进行扫描,并构成一组字符序列;
3、对每个字符的属性进行标注,方便后续再次解析并运算;
4、声明一个接口(ExperALT interface),同时声明一个数字字符结构体(type NumExpr struct),以及包含有操作符和左右节点的操作符结构体(type BinaryExpr struct);
5、然后通过解析一组字符序列,按照二叉树的后序遍历的原理,构成左右节点;
6、最后进行调用遍历并计算结果。
engine 模块
engine/alt.go
package engine
import (
"errors"
"fmt"
"strconv"
)
//设置计算符号的优先级
var tokPriority = map[string]int{"+": 0, "-": 0, "*": 1, "/": 1}
//判断二叉树节点的接口
//OperNode: 操作符节点
type ExperALT interface {
toStr() string
}
//数字表达式结构体(叶节点)
type NumExpr struct {
Val float64
Str string
}
//操作符表达式结构体(节点),二叉树的节点
type BinaryExpr struct {
Oper string //操作符 => “+”,“-”,“*”,“/”
Lhs, Rhs ExperALT
}
//ALT :algorithm token,算法结构
type ALT struct {
Tokens []*Token //扫描后的字符序列
CurTok *Token //当前字符
ExprSource string //原始表达式
CurIndex int //当前字符的下标位
Depth int //表达式的长度
Err error //错误提示
}
//实现方法
func (x *NumExpr) toStr() string {
return fmt.Sprintf("NumExpr:%s", x.Str)
}
//实现方法
func (b *BinaryExpr) toStr() string {
return fmt.Sprintf(
"BinaryExpr:(%s %s %s)",
b.Oper,
b.Lhs.toStr(),
b.Rhs.toStr(),
)
}
//初始化ALT
func NewALT(toks []*Token, s string) *ALT {
a := &ALT{
Tokens: toks,
ExprSource: s,
}
if a.Tokens == nil && len(a.Tokens) == 0 {
a.Err = errors.New("no any token")
} else {
a.CurIndex = 0
a.CurTok = a.Tokens[0]
}
return a
}
//解析扫描后的表达式
func (a *ALT) ParseExpre() ExperALT {
a.Depth++ //解析表达式,调用表达式的深度
lhs := a.ParsePrimary()
r := a.ParseOperRhs(0, lhs)
a.Depth--
if a.Depth == 0 && a.CurIndex != len(a.Tokens) && a.Err == nil {
a.Err = fmt.Errorf("no more expression or missing the operator")
}
return r
}
//获取字符的优先级
func (a *ALT) getTokPrecedence() int {
if p, ok := tokPriority[a.CurTok.Tok]; ok {
return p
}
return -1
}
//基础解析
func (a *ALT) ParsePrimary() ExperALT {
switch a.CurTok.Type {//判断字符的类型
case Liter: //如果是数字类型,则返回一个数字类型的解析
return a.ParseNumber()
case Oper:
if a.CurTok.Tok == "(" {//解析中碰到左括号,继续解析下一个字符
ch := a.getNextToken()
if ch == nil {
a.Err = fmt.Errorf("want digital but not")
return nil
}
//如果表达式为空,则已经到了尾部了
n := a.ParseExpre()
if n == nil {
return nil
}
//如果没有找到右括号,则返回一个空
if a.CurTok.Tok != ")" {
a.Err = fmt.Errorf("want `)` but not")
return nil
}else { //如果找到,则继续下一个字符
a.getNextToken()
}
return n
} else if a.CurTok.Tok == "-" || a.CurTok.Tok == "+" {
if a.getNextToken() == nil {
a.Err = fmt.Errorf("it is an oper")
return nil
}
bin := &BinaryExpr{
Oper: a.CurTok.Tok,
Lhs: &NumExpr{},
Rhs: a.ParsePrimary(), //再次解析“( )”内的数字
}
return bin
} else {
return a.ParseNumber()
}
default:
return nil
}
}
func (a *ALT) getNextToken() *Token {
a.CurIndex++
if a.CurIndex < len(a.Tokens) {
a.CurTok = a.Tokens[a.CurIndex]
return a.CurTok
}
return nil
}
func (a *ALT) ParseOperRhs(tokPrec int, lhs ExperALT) ExperALT {
for {
tokenPrio := a.getTokPrecedence()
if tokenPrio < tokPrec { //-1 < 0 ,字符为数字,返回lhs
return lhs
}
operBin := a.CurTok.Tok //如果不是数字字符,则为操作符,
if a.getNextToken() == nil {
return lhs
}
//将字符解析到二叉树右节点
rhs := a.ParsePrimary()
if rhs == nil { //操作符判断
return nil
}
//获取下一个字符的优先级
nextTokPrio := a.getTokPrecedence()
if tokenPrio < nextTokPrio {
rhs = a.ParseOperRhs(tokenPrio+1, rhs)
if rhs == nil {
return nil
}
}
lhs = &BinaryExpr{
Oper: operBin,
Lhs: lhs,
Rhs: rhs,
}
}
}
//解析数字字符
func (a *ALT) ParseNumber() *NumExpr {
f64, err := strconv.ParseFloat(a.CurTok.Tok, 64)
if err != nil {
a.Err = fmt.Errorf("get wrong number")
return &NumExpr{}
}
n := &NumExpr{
Val: f64,
Str: a.CurTok.Tok,
}
a.getNextToken()
return n
}
engine/parser.go
package engine
import (
"errors"
"strings"
)
//定义一组常量,用来区分数字字符与操作符
const (
Liter = iota
Oper
)
//定义单个字符的结构体
type Token struct {
Tok string
Type int // 标注字符的类型是Liter/Oper
Offset int // 当前该字符的下标位置
}
//定义一个用来解析一组字符序列的结构体
type Parser struct {
Source string //原表达式
ch byte //当前字符所在的位置
index int //当前字符对象所在字符序列中的位置
err error //错误提示
}
//初始化
func Parse(s string) ([]*Token, error) {
p := &Parser{
Source: s,
err: nil,
ch: s[0],
}
toks := p.parse()
if p.err != nil {
return nil, p.err
}
return toks, nil
}
//编写一个方法,返回一组字符序列
func (p *Parser) parse() []*Token {
toks := make([]*Token, 0)
for {
tok := p.nextTok()
if tok == nil {
break
}
toks = append(toks, tok)
}
return toks
}
//不断地扫描解析下一个字符,返回一个字符对象
func (p *Parser) nextTok() *Token {
if p.index >= len(p.Source) || p.err != nil {
return nil
}
var (
err error
tok *Token
)
//循环判断该字符是否存在不规范的空隙或者其他字符
for p.isBlank(p.ch) && err == nil {
err = p.nextCh()
}
//定位到当前的字符
start := p.index
switch p.ch {
case '(', ')', '+','-','*','/':
tok = &Token{
Tok: string(p.ch),
Type: Oper,
}
//标记字符的下标位置
tok.Offset = start
err = p.nextCh()
if err != nil {
p.err = err
}
case '0','1','2','3','4','5','6','7','8','9':
//多位数组合
for p.isDigitNum(p.ch) && p.nextCh() == nil {
if (p.ch == '-' || p.ch == '+' || p.ch == '/' || p.ch == '*') {
break
}
}
//去除表达式各字符之间的空格
tok = &Token{
Tok: strings.Trim(p.Source[start:p.index],""),
Type: Liter,
}
//标记下标位置
tok.Offset = start
default:
}
//fmt.Print(tok)
return tok
}
//扫描下一个字符对象
func (p *Parser) nextCh() error {
p.index++
if p.index < len(p.Source) {
p.ch = p.Source[p.index]
return nil
}
return errors.New("EOF")
}
//判断字符之间是否存在不合规的字符
func (p *Parser) isBlank(c byte) bool {
return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'
}
//判断是否为数字字符,因为是浮点计算,因此“.”,“e”也属于数字字符。
func (p *Parser) isDigitNum(c byte) bool {
return '0' <= c && c <= '9' || c == '.' || c == '_' || c == 'e'
}
engine/utils.go
package engine
import (
//"errors"
"fmt"
"math/big"
"strconv"
)
//转化(float64 -> string)
func Float64ToStr(f float64) string {
return strconv.FormatFloat(f, 'f', -1, 64)
}
//计算表达式的结果
func CalExpreToResult(expr ExperALT) float64 {
var l, r float64
switch expr.(type) {// 通过接口断言,如果包含有操作符,则进行计算并输出结果
case *BinaryExpr:
alt :=expr.(*BinaryExpr)
//递归调用,类似后序遍历(先左节点,再右节点,然后是中节点(root))
l = CalExpreToResult(alt.Lhs)
r = CalExpreToResult(alt.Rhs)
switch alt.Oper {
case "+":
lh, _ := new(big.Float).SetString(Float64ToStr(l))
rh, _ := new(big.Float).SetString(Float64ToStr(r))
f, _ := new(big.Float).Add(lh, rh).Float64()
return f
case "-":
lh, _ := new(big.Float).SetString(Float64ToStr(l))
rh, _ := new(big.Float).SetString(Float64ToStr(r))
f, _ := new(big.Float).Sub(lh, rh).Float64()
return f
case "*":
lh, _ := new(big.Float).SetString(Float64ToStr(l))
rh, _ := new(big.Float).SetString(Float64ToStr(r))
f, _ := new(big.Float).Mul(lh,rh ).Float64()
return f
case "/":
if r == 0 {
panic(fmt.Errorf("denominator cannot be zero: [%g/%g]",l,r))
}
f, _ := new(big.Float).Quo(new(big.Float).SetFloat64(l),
new(big.Float).SetFloat64(r)).Float64()
return f
default:
}
case *NumExpr:
return expr.(*NumExpr).Val
default:
}
return 0.0
}
main.go
package main
import (
"bufio"
"fmt"
".../engine"
"os"
"strings"
"time"
)
func main(){
Run()
}
func Run() {
for {
fmt.Print("input expression:=> ")
f := bufio.NewReader(os.Stdin)
s, err := f.ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
s = strings.TrimSpace(s)
if s == "" {
continue
}
if s == "exit" || s == "quit" || s == "q" {
fmt.Println("thanks for using,bye-bye!")
break
}
//测试一下运行的时间
start := time.Now()
run(s)
cost := time.Since(start)
fmt.Println("time: " + cost.String())
}
}
func run(expr string) {
// input text -> []token
toks, err := engine.Parse(expr)
if err != nil {
fmt.Println("ERROR: " + err.Error())
return
}
//初始化
alt := engine.NewALT(toks, expr)
if alt.Err != nil {
fmt.Println("ERROR: " + alt.Err.Error())
return
}
// 构造解析
ar := alt.ParseExpre()
if alt.Err != nil {
fmt.Println("ERROR: " + alt.Err.Error())
return
}
fmt.Printf("ExprALT: %+v\n", ar)
// 捕获错误
defer func() {
if err := recover(); err != nil {
fmt.Println("ERROR: ", err)
}
}()
// 调用并计算出结果 -> result
r := engine.CalExpreToResult(ar)
fmt.Println("progressing ...\t", r)
fmt.Printf("%s = %v\n", expr, r)
}
输出结果: