Go 学习笔记-Go 词法解析

简介: Go 学习笔记-Go 词法解析

对于 go 语言源代码可以看做是一个很长的字符串,GO 编译器是如何识别出这个长字符串里边哪些是变量?哪些是赋值语句?哪些是函数?基本解决思路就是使用正则表达式对字符串进行分割,找到对应的标识,可以将这种标识转化为 token,将 go 源代码扫描找出所有 token 的过程称为词法分析。

1.正则表达式举例

对于一个正则表达式 (a|b)*abb,应该如何判断一个字符串对于该正则表达式满足呢?例如:

abb
aabb
babb
aababb
//肉眼可以看出上面字符串是符合上述正则表达式的
a
ab
bb
acabb
//肉眼可以看出上面字符串是不符合上述正则表达式的
abbaabbbaaabbbababaaababb
//对于很长的字符串是否满足上述正则表达式,需要使用程序代码来实现,如何实现一个正则匹配的算法呢?

2.不确定有穷自动机(NFA)

对于一个正则表达式 (a|b)*abb,其对应的 NFA 对应如下:

这个自动机包含 4 个状态,其中 0、1、2 这 3 种状态用一个圈表示中间状态,3 是双圈表示状态机的结束,箭头和字母表示每个状态遇到不同输入迁移到另一个状态,可以用上述例子走一下该自动机,比如字符串 abb,初始状态 0,遇到 a,迁移到 1,状态 1 遇到 b,迁移到 2,状态 2 遇到 b,迁移到 3,状态 3 是结束状态,所以 abb 是满足正则表达式。对于 字符串 ab 最终状态是 2,不是结束状态,不满足正则表达式。但该有穷自动机有一个缺陷,就是状态 0 遇到 a,有可能迁移到状态 1,也有可能迁移到到本身状态 0,所以这里是不确定的有穷自动机,这就导致可能满足表达式的字符串,最终状态并没有到结束状态。

3.确定有穷自动机(DFA)

DFA 和 NFA 不同的是,对于每一个状态遇到的输入都有确定下一个状态,可以使用 re2c 这种工具帮助我们生成正则表达式对应的有穷自动机程序代码。

4.用 re2c 做词法解析

re2c 官网中介绍 go 词法解析的地址是 re2c官网,使用 git 命令可下载 re2c 源码包:

git clone https://github.com/skvadrik/re2c.git
git clone https://git.code.sf.net/p/re2c/code-git

如果是 mac 电脑需要先使用 brew install automake 命令安装 automake, 下载后进入到 re2c 的源码目录下包下执行如下命令就可生成 configure 文件:

进入到 re2c 目录下执行 ./configure && make && make install 命令即可开始编译 re2c :

编译安装完之后,执行 re2c 命令出现如下图所示内容表示安装成功:

提示需要指定源文件,这里编辑一个 t.go 文件,用于判断某个字符是不是二进制数,编辑内容如下:

//go:generate re2go $INPUT -o $OUTPUT -i
package main
func lex(str string) {
    var cursor int
    /*!re2c
      re2c:define:YYCTYPE = char;
      re2c:yyfill:enable = 0;
      end = "\x00";
      bin = '0b'[01]+;
      * { return ERR; }
      bin end { return BIN; }
    */
}
func main() {
    lex("0b1")
}

上面 /*!re2c*/ 之间的内容可以理解为词法解析的正则表达式,这里可以根据自己的需要自己编写正则表达式,re2c 代码库会帮我们生成有限自动机程序代码,然后使用 re2c t.go 命令,可生成与之对应的有限自动机:

生成的与之对应的有限自动机(DFA)程序代码如下:

/* Generated by re2c 2.2 on Thu Oct 21 00:13:40 2021 */
#line 1 "t.go"
//go:generate re2go $INPUT -o $OUTPUT -i
package main
func lex(str string) {
    var cursor int
#line 12 "<stdout>"
{
    char yych;
    yych = *YYCURSOR;
    switch (yych) {
    case '0':    goto yy4;
    default:    goto yy2;
    }
yy2:
    ++YYCURSOR;
yy3:
#line 13 "t.go"
    { return ERR; }
#line 25 "<stdout>"
yy4:
    yych = *(YYMARKER = ++YYCURSOR);
    switch (yych) {
    case 'B':
    case 'b':    goto yy5;
    default:    goto yy3;
    }
yy5:
    yych = *++YYCURSOR;
    if (yych >= 0x01) goto yy8;
yy6:
    YYCURSOR = YYMARKER;
    goto yy3;
yy7:
    yych = *++YYCURSOR;
yy8:
    switch (yych) {
    case 0x00:    goto yy9;
    case '0':
    case '1':    goto yy7;
    default:    goto yy6;
    }
yy9:
    ++YYCURSOR;
#line 14 "t.go"
    { return BIN; }
#line 52 "<stdout>"
}
#line 15 "t.go"
}
func main() {
    lex("0b1")
}

对应的有限自动机图如下图:

5.go 词法解析demo

package main
import (
 "fmt"
 "go/scanner"
 "go/token"
)
func main()  {
 src := []byte("cos(x) + 2i*sin(x) //Euler")
 //初始化 scanner
 var s scanner.Scanner
 fset := token.NewFileSet()
 file := fset.AddFile("",fset.Base(),len(src))
 s.Init(file,src,nil,scanner.ScanComments)
 //扫描
 for  {
  pos,tok,lit := s.Scan()
  if tok == token.EOF{
   break
  }
  fmt.Println("%s\t%s\t%q\n",fset.Position(pos),tok,lit)
 }
}

其中字符串 “cos(x) + 2i*sin(x) //Euler” 可以理解为源代码,输出结果就是将其词法解析,然后 token 化,至此词法解析已经简单实现:

源代码 token 化后,就可进行语法解析,后面将继续介绍语法解析和抽象语法树构建。

相关文章
|
1月前
|
Cloud Native 安全 Java
Go语言深度解析:从入门到精通的完整指南
🌟蒋星熠Jaxonic,Go语言探索者。深耕云计算、微服务与并发编程,以代码为笔,在二进制星河中书写极客诗篇。分享Go核心原理、性能优化与实战架构,助力开发者掌握云原生时代利器。#Go语言 #并发编程 #性能优化
384 43
Go语言深度解析:从入门到精通的完整指南
|
3月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
322 0
|
2月前
|
Cloud Native 安全 Java
Go语言深度解析:从入门到精通的完整指南
🌟 蒋星熠Jaxonic,执着的星际旅人,用Go语言编写代码诗篇。🚀 Go语言以简洁、高效、并发为核心,助力云计算与微服务革新。📚 本文详解Go语法、并发模型、性能优化与实战案例,助你掌握现代编程精髓。🌌 从goroutine到channel,从内存优化到高并发架构,全面解析Go的强大力量。🔧 实战构建高性能Web服务,展现Go在云原生时代的无限可能。✨ 附技术对比、最佳实践与生态全景,带你踏上Go语言的星辰征途。#Go语言 #并发编程 #云原生 #性能优化
|
7月前
|
算法 Go 索引
【LeetCode 热题100】45:跳跃游戏 II(详细解析)(Go语言版)
本文详细解析了力扣第45题“跳跃游戏II”的三种解法:贪心算法、动态规划和反向贪心。贪心算法通过选择每一步能跳到的最远位置,实现O(n)时间复杂度与O(1)空间复杂度,是面试首选;动态规划以自底向上的方式构建状态转移方程,适合初学者理解但效率较低;反向贪心从终点逆向寻找最优跳点,逻辑清晰但性能欠佳。文章对比了各方法的优劣,并提供了Go语言代码实现,助你掌握最小跳跃次数问题的核心技巧。
321 15
|
7月前
|
机器学习/深度学习 存储 算法
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
476 90
|
3月前
|
缓存 监控 安全
告别缓存击穿!Go 语言中的防并发神器:singleflight 包深度解析
在高并发场景中,多个请求同时访问同一资源易导致缓存击穿、数据库压力过大。Go 语言提供的 `singleflight` 包可将相同 key 的请求合并,仅执行一次实际操作,其余请求共享结果,有效降低系统负载。本文详解其原理、实现及典型应用场景,并附示例代码,助你掌握高并发优化技巧。
299 0
|
3月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
5月前
|
存储 设计模式 安全
Go 语言单例模式全解析:从青铜到王者段位的实现方案
单例模式确保一个类只有一个实例,并提供全局访问点,适用于日志、配置管理、数据库连接池等场景。在 Go 中,常用实现方式包括懒汉模式、饿汉模式、双重检查锁定,最佳实践是使用 `sync.Once`,它并发安全、简洁高效。本文详解各种实现方式的优缺点,并提供代码示例与最佳应用建议。
187 5
|
6月前
|
存储 算法 Go
【LeetCode 热题100】17:电话号码的字母组合(详细解析)(Go语言版)
LeetCode 17题解题思路采用回溯算法,通过递归构建所有可能的组合。关键点包括:每位数字对应多个字母,依次尝试;递归构建下一个字符;递归出口为组合长度等于输入数字长度。Go语言实现中,使用map存储数字到字母的映射,通过回溯函数递归生成组合。时间复杂度为O(3^n * 4^m),空间复杂度为O(n)。类似题目包括括号生成、组合、全排列等。掌握回溯法的核心思想,能够解决多种排列组合问题。
259 11
|
6月前
|
Go
【LeetCode 热题100】155:最小栈(详细解析)(Go语言版)
本文详细解析了力扣热题155:最小栈的解题思路与实现方法。题目要求设计一个支持 push、核心思路是使用辅助栈法,通过两个栈(主栈和辅助栈)来维护当前栈中的最小值。具体操作包括:push 时同步更新辅助栈,pop 时检查是否需要弹出辅助栈的栈顶,getMin 时直接返回辅助栈的栈顶。文章还提供了 Go 语言的实现代码,并对复杂度进行了分析。此外,还介绍了单栈 + 差值记录法的进阶思路,并总结了常见易错点,如 pop 操作时忘记同步弹出辅助栈等。
219 6

推荐镜像

更多
  • DNS