Go语言实现表达式求值器的秘密都在这里!

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: Go语言实现表达式求值器的秘密都在这里!

Go 语言表达式求值器

 

一、概述

表达式求值器是一类常见的计算机程序,它可以解析字符串表达式,求出表达式的值。本文将介绍如何使用 Go 语言来实现一个表达式求值器。

主要内容包括:

  • 表达式求值器介绍
  • 需求分析
  • 总体设计
  • 词法解析
  • 语法解析
  • 求值计算
  • 测试用例
  • 扩展实现
  • 总结

希望通过本文可以让读者了解如何使用 Go 语言实现一个简单的表达式求值器。


 

二、表达式求值器介绍

表达式求值器可以接受一个字符串表达式,对其求值后返回结果。

例如输入表达式:"1 + 2",求值器会计算出结果 3。

表达式中常见的可计算元素包括:

  • 数字:1, 2.5, 3.14 等
  • 变量:x, y, z 等
  • 运算符:+ - * /等
  • 函数:abs(), max()等
  • 括号表示优先级:(...)

一个完整的表达式求值器还需要支持更多特性,比如函数调用、更多的数据类型等。


 

三、需求分析

需要实现一个简单的算术表达式求值器,具有如下功能需求:

  • 支持整数浮点数
  • 支持+ - * /运算
  • 支持小括号表示优先级
  • 支持变量
  • 可以扩展新的运算符

输入一个表达式,输出求值结果。


 

四、总体设计

表达式求值可以分为以下步骤:

  1. 词法分析:将表达式字符串分解成词法单元(数字、符号、变量等)
  2. 语法分析:构建语法树
  3. 求值:对语法树进行运算求值


 

五、词法分析

词法分析将字符串表达式分割为词法单元,也就是编程语言中最小的语义单位,常见的词法单元有:

  • 数字:1, 1.2, .3, 3.14
  • 运算符:+ - * /
  • 括号:( )
  • 变量:x y z
  • 分隔符:, ; 等
package main
import (
    "fmt"
    "regexp"
)
func main() {
    pattern := `\d+|\w+|[\*\+\-\(\)]`
    lex := regexp.MustCompile(pattern)
    tokens := lex.FindAllString("1 + (2 - 3) * 4", -1)
    fmt.Println("Tokens:")
    for _, token := range tokens {
        fmt.Println(token)
    }
}

六、语法解析

在词法分析的基础上,使用递归下降解析方法构建语法树。

递归下降解析是一种非常简单和常见的语法解析方法。

递归下降解析器的工作可以分解为以下步骤:

  1. 如果遇到终结符(数字、变量等),返回终结符
  2. 对于运算符,匹配运算优先级,构造语法树节点
  3. 使用递归调用继续解析剩余表达式

递归下降解析不需要单独的栈,通过函数调用栈实现解析。


 

七、求值计算

有了语法树后,可以递归遍历语法树,进行求值计算。

对树节点进行运算求值:

  • 叶子节点是数字或变量,直接返回值
  • 一元运算符节点,计算子节点的值
  • 二元运算符,计算左右子节点的值
  • 返回根节点的值

通过递归求值,可以处理所有复杂的嵌套计算。


 

八、测试用例

编写表达式测试用例,covering 不同的语法场景

package main
import (
  "bufio"
  "fmt"
  "os"
  "strconv"
  "strings"
)
// Node 结构表示解析树的节点
type Node struct {
  Left, Right *Node  // 左右子节点
  Op          string // 操作符(+、-、*、/)或空字符串(叶子节点)
  Val         int    // 叶子节点的值
}
// parse 函数接受一个字符串数组 tokens,返回解析后的根节点
func parse(tokens []string) *Node {
  var index int
  return parseExpression(tokens, &index)
}
// parseExpression 解析加法和减法表达式
func parseExpression(tokens []string, index *int) *Node {
  left := parseTerm(tokens, index)
  // 处理加法和减法运算符
  for *index < len(tokens) && 
  (tokens[*index] == "+" || tokens[*index] == "-") {
    op := tokens[*index]
    (*index)++
    right := parseTerm(tokens, index)
    left = &Node{Left: left, Right: right, Op: op}
  }
  return left
}
// parseTerm 解析乘法和除法表达式
func parseTerm(tokens []string, index *int) *Node {
  left := parseFactor(tokens, index)
  // 处理乘法和除法运算符
  for *index < len(tokens) &&
   (tokens[*index] == "*" || tokens[*index] == "/") {
    op := tokens[*index]
    (*index)++
    right := parseFactor(tokens, index)
    left = &Node{Left: left, Right: right, Op: op}
  }
  return left
}
// parseFactor 解析数字或括号内的表达式
func parseFactor(tokens []string, index *int) *Node {
  token := tokens[*index]
  (*index)++
  if token == "(" {
    // 如果遇到左括号,递归解析括号内的表达式
    node := parseExpression(tokens, index)
    if tokens[*index] != ")" {
      panic("Expected closing parenthesis")
    }
    (*index)++
    return node
  }
  // 如果是数字,创建一个叶子节点
  val, err := strconv.Atoi(token)
  if err != nil {
    panic("Invalid number")
  }
  return &Node{Val: val}
}
// evaluate 函数计算解析树的值
func evaluate(node *Node) int {
  if node.Op == "" {
    // 叶子节点,返回其值
    return node.Val
  }
  // 递归计算左右子树的值
  leftValue := evaluate(node.Left)
  rightValue := evaluate(node.Right)
  // 根据操作符执行相应的运算
  switch node.Op {
  case "+":
    return leftValue + rightValue
  case "-":
    return leftValue - rightValue
  case "*":
    return leftValue * rightValue
  case "/":
    if rightValue == 0 {
      panic("Division by zero")
    }
    return leftValue / rightValue
  default:
    panic("Unknown operator: " + node.Op)
  }
}
func main() {
  reader := bufio.NewReader(os.Stdin)
  // 循环等待用户输入表达式
  for {
    fmt.Print("输入一个表达式(或输入'exit'退出):")
    input, _ := reader.ReadString('\n')
    input = strings.TrimSpace(input)
    if input == "exit" {
      break
    }
    // 将输入的字符串分割为单词
    tokens := strings.Fields(input)
    // 解析并计算表达式的值
    ast := parse(tokens)
    result := evaluate(ast)
    // 输出计算结果
    fmt.Println("Result:", result)
  }
}

并验证求值器求出的结果是否正确。


 

九、扩展实现

可以针对求值器进行各种扩展:

  • 支持更多数据类型:字符串、布尔值等
  • 扩展运算符:- 加法、取余数等
  • 支持函数调用:abs(), max()等
  • 支持变量赋值运算
  • 更强大的语法分析模块


 

总结

本文介绍了如何使用 Go 语言实现一个简单的表达式求值器。涵盖了词法解析、语法解析和求值计算三个关键模块。可以作为编写解释器和编译器的基础。

通过扩展求值器的运算符和语法,可以得到一个功能更丰富的计算平台。


目录
相关文章
|
14天前
|
存储 监控 算法
员工上网行为监控中的Go语言算法:布隆过滤器的应用
在信息化高速发展的时代,企业上网行为监管至关重要。布隆过滤器作为一种高效、节省空间的概率性数据结构,适用于大规模URL查询与匹配,是实现精准上网行为管理的理想选择。本文探讨了布隆过滤器的原理及其优缺点,并展示了如何使用Go语言实现该算法,以提升企业网络管理效率和安全性。尽管存在误报等局限性,但合理配置下,布隆过滤器为企业提供了经济有效的解决方案。
57 8
员工上网行为监控中的Go语言算法:布隆过滤器的应用
|
1月前
|
存储 Go 索引
go语言中数组和切片
go语言中数组和切片
42 7
|
1月前
|
Go 开发工具
百炼-千问模型通过openai接口构建assistant 等 go语言
由于阿里百炼平台通义千问大模型没有完善的go语言兼容openapi示例,并且官方答复assistant是不兼容openapi sdk的。 实际使用中发现是能够支持的,所以自己写了一个demo test示例,给大家做一个参考。
|
1月前
|
程序员 Go
go语言中结构体(Struct)
go语言中结构体(Struct)
103 71
|
1月前
|
存储 Go 索引
go语言中的数组(Array)
go语言中的数组(Array)
107 67
|
1月前
|
Go 索引
go语言for遍历数组或切片
go语言for遍历数组或切片
106 62
|
1月前
|
并行计算 安全 Go
Go语言中的并发编程:掌握goroutines和channels####
本文深入探讨了Go语言中并发编程的核心概念——goroutine和channel。不同于传统的线程模型,Go通过轻量级的goroutine和通信机制channel,实现了高效的并发处理。我们将从基础概念开始,逐步深入到实际应用案例,揭示如何在Go语言中优雅地实现并发控制和数据同步。 ####
|
9天前
|
算法 安全 Go
Go 语言中实现 RSA 加解密、签名验证算法
随着互联网的发展,安全需求日益增长。非对称加密算法RSA成为密码学中的重要代表。本文介绍如何使用Go语言和[forgoer/openssl](https://github.com/forgoer/openssl)库简化RSA加解密操作,包括秘钥生成、加解密及签名验证。该库还支持AES、DES等常用算法,安装简便,代码示例清晰易懂。
42 12
|
1月前
|
存储 Go
go语言中映射
go语言中映射
38 11
|
1月前
|
Go
go语言for遍历映射(map)
go语言for遍历映射(map)
40 12