images/blog-posts

递归向下算法实现Calc

返回教程主页

上篇 使用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记录运算符号,lhsrhs分别为表达式左、右两边的内容,如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判断lhsrhs的值究竟以怎样的方式进行计算。

定义语法解析的入口函数

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

下篇 使计算器支持语句块

SUBSCRIBE


🔒 No spam. Unsubscribe any time.

About kk

kk

Vincenzo Antedoro is an engineer who helps those who want to invest in renewables. For the rest he enjoys teaching with the method of learning by doing..

» More about kk