自制程序语言・递归向下算法实现Calc

利用递归向下算法结合我们的BKLexer实现支持四则运算与括号优先级的计算器程序。

代码清单【Golang】

go 复制代码
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 复制代码
➜ go calc.go 

Hello My Calc.
> 1 + (2 - 3) * 4 / 5      
out = 0.19999999999999996
> quit
bye!

引入需要的包

go 复制代码
import (
    "fmt"
    "os"
    "bufio"
    "strings"
    "strconv"
    "./bklexer"
)
  • fmt 打印输出
  • os + bufio 读取用户输入
  • strings 处理字符串
  • strconv 解析字符串
  • bklexer 用于词法分析

定义接口

go 复制代码
type Node interface {
    GetValue() float64
}

Node作为接口将用于指向其它的结构,其GetValue方法可以获得节点的数值。

定义数字节点

go 复制代码
type Number struct {
    value float64
}

func NewNumber(token *BKLexer.Token) *Number {
    value, _ := strconv.ParseFloat(token.Source, 64)
    return &Number{value: value}
}

Number作为数字类型的节点,有成员value用于存储数值,使用NewNumber函数可以实例它。

实现数字节点的接口方法

go 复制代码
func (number *Number) GetValue() float64 {
    return number.value
}

定义运算操作节点

go 复制代码
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记录运算符号,lhsrhs分别为表达式左、右两边的内容,如1+2中的1与2。使用NewBinaryOpt函数实例它。

实现运算节点的接口方法

go 复制代码
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判断lhsrhs的值究竟以怎样的方式进行计算。

定义语法解析的入口函数

go 复制代码
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否则返回解析结果。

定义加法等级运算操作的解析函数

go 复制代码
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构建当前运算等级的节点对象。

定义乘法等级运算操作的解析函数

go 复制代码
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一样,只是在运行开始是尝试解析数字或括号表达式的内容,在判断需要构建乘法等级运算节点的时候也是尝试解析另一个数字或括号表达式的内容。

定义数字与括号表达式的解析函数

go 复制代码
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

定义词法分析器规则

go 复制代码
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]+")

循环读取用户输入并解析计算

go 复制代码
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函数计算表达式并得到结果。

相关推荐
小码农<^_^>9 分钟前
优选算法精品课--滑动窗口算法(一)
算法
羊小猪~~11 分钟前
神经网络基础--什么是正向传播??什么是方向传播??
人工智能·pytorch·python·深度学习·神经网络·算法·机器学习
软工菜鸡36 分钟前
预训练语言模型BERT——PaddleNLP中的预训练模型
大数据·人工智能·深度学习·算法·语言模型·自然语言处理·bert
南宫生39 分钟前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
AI视觉网奇1 小时前
sklearn 安装使用笔记
人工智能·算法·sklearn
JingHongB1 小时前
代码随想录算法训练营Day55 | 图论理论基础、深度优先搜索理论基础、卡玛网 98.所有可达路径、797. 所有可能的路径、广度优先搜索理论基础
算法·深度优先·图论
weixin_432702261 小时前
代码随想录算法训练营第五十五天|图论理论基础
数据结构·python·算法·深度优先·图论
小冉在学习1 小时前
day52 图论章节刷题Part04(110.字符串接龙、105.有向图的完全可达性、106.岛屿的周长 )
算法·深度优先·图论
Repeat7152 小时前
图论基础--孤岛系列
算法·深度优先·广度优先·图论基础
小冉在学习2 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论