Go: 用go编写一个简易版计算器

对go的喜欢已经超过了C/C ++ 😂,继续尝试着用go来完成一个包含有括号及加、减、乘、除的简易版计算器。并可以实现浮点计算。中间还利用到二叉树的后序遍历。

基本思路:

1、从终端输入表达式,并读取,先进行去除空格等操作;

2、将处理好的表达式进行扫描,并构成一组字符序列;

3、对每个字符的属性进行标注,方便后续再次解析并运算;

4、声明一个接口(ExperALT interface),同时声明一个数字字符结构体(type NumExpr struct),以及包含有操作符和左右节点的操作符结构体(type BinaryExpr struct);

5、然后通过解析一组字符序列,按照二叉树的后序遍历的原理,构成左右节点;

6、最后进行调用遍历并计算结果。

有兴趣的可以参考更加复杂的带功能计算的写法:math-engine/engine at master · dengsgo/math-engine · GitHubMathematical expression parsing and calculation engine library. 数学表达式解析计算引擎库 - math-engine/engine at master · dengsgo/math-enginehttps://github.com/dengsgo/math-engine/blob/master/engine

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)
}

输出结果:

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值