snakeyaml-ast 是一个纯 Go 实现的 YAML 解析器,能够生成与 Java SnakeYAML 100% 兼容的 Token 流和 AST(抽象语法树)。
如果你需要在 Go 中对 YAML 进行精确的语法分析(而不仅仅是反序列化到结构体),或者需要与 Java SnakeYAML 的输出保持一致,那么这个库就是你需要的。
- 100% Token 兼容 — 与 Java SnakeYAML 产生完全一致的 Token 流(205/205 测试通过)
- 高度 AST 兼容 — AST 输出与 Java SnakeYAML 一致(145/205 测试通过,剩余为 Java 端 StackOverflow 或自定义 Tag 导致的不可测用例)
- 纯 Go 实现 — 零外部依赖,仅使用 Go 标准库
- 完整的 YAML 1.1 支持 — 包括锚点/别名、多文档、流式/块式集合、所有标量样式
- 干净的公共 API — 提供
Parse、ParseAll、Walk三个核心函数,用法极其简洁 - 源码位置追踪 — 每个节点都记录了在源文件中的起止位置(行、列、偏移量)
go get github.com/weaweawe01/snakeyaml-astpackage main
import (
"fmt"
snakeyaml "github.com/weaweawe01/snakeyaml-ast"
)
func main() {
root, err := snakeyaml.Parse("name: Alice\nage: 30")
if err != nil {
panic(err)
}
fmt.Printf("根节点类型: %s, Tag: %s\n", root.Kind, root.Tag)
// 输出: 根节点类型: mapping, Tag: tag:yaml.org,2002:map
}package main
import (
"fmt"
snakeyaml "github.com/weaweawe01/snakeyaml-ast"
)
func main() {
str := `
name: Alice
age: 30
hobbies:
- reading
- coding
address:
city: Shanghai
zip: "200000"
`
root, err := snakeyaml.Parse(str)
if err != nil {
fmt.Println("parse error:", err)
return
}
snakeyaml.Walk(root, func(path string, n *snakeyaml.Node) {
switch n.Kind {
case snakeyaml.ScalarNode:
fmt.Printf("%s => %s tag=%s value=%q\n", path, n.Kind, n.Tag, n.Value)
default:
fmt.Printf("%s => %s tag=%s\n", path, n.Kind, n.Tag)
}
})
}输出:
root => mapping tag=tag:yaml.org,2002:map
root{0}.key => scalar tag=tag:yaml.org,2002:str value="name"
root{0}.value => scalar tag=tag:yaml.org,2002:str value="Alice"
root{1}.key => scalar tag=tag:yaml.org,2002:str value="age"
root{1}.value => scalar tag=tag:yaml.org,2002:int value="30"
root{2}.key => scalar tag=tag:yaml.org,2002:str value="hobbies"
root{2}.value => sequence tag=tag:yaml.org,2002:seq
root{2}.value[0] => scalar tag=tag:yaml.org,2002:str value="reading"
root{2}.value[1] => scalar tag=tag:yaml.org,2002:str value="coding"
root{3}.key => scalar tag=tag:yaml.org,2002:str value="address"
root{3}.value => mapping tag=tag:yaml.org,2002:map
root{3}.value{0}.key => scalar tag=tag:yaml.org,2002:str value="city"
root{3}.value{0}.value => scalar tag=tag:yaml.org,2002:str value="Shanghai"
root{3}.value{1}.key => scalar tag=tag:yaml.org,2002:str value="zip"
root{3}.value{1}.value => scalar tag=tag:yaml.org,2002:str value="200000"
nodes, err := snakeyaml.ParseAll("---\na: 1\n---\nb: 2")
if err != nil {
panic(err)
}
fmt.Printf("文档数量: %d\n", len(nodes))
// 输出: 文档数量: 2解析 YAML 字符串,返回第一个文档的 AST 根节点。如果输入为空或无效,返回错误。
解析包含多个文档的 YAML 字符串(以 --- 分隔),返回每个文档的 AST 根节点切片。
深度优先遍历 AST 树,对每个节点调用回调函数 fn。path 参数描述节点在树中的位置:
| 路径格式 | 含义 |
|---|---|
root |
根节点 |
root[0] |
序列的第 0 个元素 |
root{0}.key |
映射的第 0 对键值对的键 |
root{0}.value |
映射的第 0 对键值对的值 |
type Node struct {
Kind NodeKind // 节点类型:ScalarNode / SequenceNode / MappingNode
Tag string // YAML Tag,如 "tag:yaml.org,2002:str"
Value string // 标量值(仅 ScalarNode 有值)
Anchor string // 锚点名称(如 &anchor)
Style ScalarStyle // 标量引号样式(仅 ScalarNode)
FlowStyle FlowStyle // 集合样式(仅 SequenceNode / MappingNode)
StartPos Mark // 节点在源码中的起始位置
EndPos Mark // 节点在源码中的结束位置
Children []*Node // 子节点列表(仅 SequenceNode)
Mappings []KeyValue // 键值对列表(仅 MappingNode)
}| 值 | 含义 |
|---|---|
ScalarNode |
标量值(字符串、数字、布尔等) |
SequenceNode |
YAML 序列(列表) |
MappingNode |
YAML 映射(字典) |
| 值 | YAML 示例 |
|---|---|
PlainStyle |
hello |
SingleQuotedStyle |
'hello' |
DoubleQuotedStyle |
"hello" |
LiteralStyle |
| hello |
FoldedStyle |
> hello |
| 值 | YAML 示例 |
|---|---|
FlowAuto |
自动(默认块式) |
FlowBlock |
块式(缩进式) |
FlowFlow |
流式([a, b] 或 {a: 1}) |
type Mark struct {
Line int // 行号(0 起始)
Column int // 列号(0 起始)
Index int // 字符偏移量(绝对位置)
}type KeyValue struct {
Key *Node
Value *Node
}type Error struct {
Message string
}const (
TagPrefix = "tag:yaml.org,2002:"
TagStr = "tag:yaml.org,2002:str"
TagInt = "tag:yaml.org,2002:int"
TagFloat = "tag:yaml.org,2002:float"
TagBool = "tag:yaml.org,2002:bool"
TagNull = "tag:yaml.org,2002:null"
TagSeq = "tag:yaml.org,2002:seq"
TagMap = "tag:yaml.org,2002:map"
TagSet = "tag:yaml.org,2002:set"
TagMerge = "tag:yaml.org,2002:merge"
TagTimestamp = "tag:yaml.org,2002:timestamp"
TagBinary = "tag:yaml.org,2002:binary"
TagOmap = "tag:yaml.org,2002:omap"
TagPairs = "tag:yaml.org,2002:pairs"
TagYAML = "tag:yaml.org,2002:yaml"
)可用于判断节点的 Tag 类型:
if n.Tag == snakeyaml.TagInt {
fmt.Println("这是一个整数节点:", n.Value)
}本项目严格复刻了 Java SnakeYAML 的四阶段处理流水线:
YAML 文本
│
▼
┌──────────┐
│ Reader │ ── 读取 UTF-8 字符流,规范化换行,验证可打印字符,追踪源码位置
└──────────┘
│ rune 流
▼
┌──────────┐
│ Scanner │ ── 词法分析:将字符流转为 Token 流(21+ 种 Token 类型)
└──────────┘ 处理缩进规则、块标量、Flow 集合等
│ Token 流
▼
┌──────────┐
│ Parser │ ── 语法分析:将 Token 流转为 Event 流(11 种 Event 类型)
└──────────┘ 实现 YAML 语法规则的 27 个产生式状态
│ Event 流
▼
┌──────────┐
│ Composer │ ── 组合阶段:将 Event 流构建为 AST 节点树
└──────────┘ 处理锚点/别名引用、Tag 解析、嵌套深度控制
│ internal Node 树
▼
┌──────────┐
│ Public API│ ── convertNode() 将内部类型转换为公共 API 类型
└──────────┘
│
▼
snakeyaml.Node AST(用户可用)
| 包 | 职责 |
|---|---|
internal/reader |
UTF-8 字符流读取,换行符规范化,可打印字符验证,源位置追踪 |
internal/scanner |
词法分析器,实现 YAML 缩进规则和特殊 Token 检测,产生 21+ 种 Token |
internal/token |
定义 Token 类型(StreamStart/End、Scalar、Anchor、Tag 等)和 ScalarStyle 枚举 |
internal/parser |
语法分析器,将 Token 转为 Event,使用 Go 闭包实现 27 个产生式状态 |
internal/event |
定义 Event 类型(StreamStart/End、DocumentStart/End、Scalar、Alias 等) |
internal/composer |
从 Event 流构建 AST 节点树,处理锚点/别名、Tag 解析 |
internal/node |
内部 AST 节点表示(Scalar/Sequence/Mapping),由公共 API 转换后输出 |
internal/resolver |
隐式 Tag 解析器,使用正则匹配 bool/int/float/null/timestamp 等类型 |
internal/model |
JSON 可序列化的数据结构,用于黄金文件测试比对 |
命令行工具,支持将 YAML 文件导出为 Token 流或 AST 表示:
# 导出 Token 流
go run ./cmd/goyaml-dump -mode token -input test.yaml
# 导出 AST
go run ./cmd/goyaml-dump -mode ast -input test.yaml
# 从 stdin 读取,输出到文件
cat test.yaml | go run ./cmd/goyaml-dump -mode ast -output result.txt参数说明:
| 参数 | 默认值 | 说明 |
|---|---|---|
-mode |
ast |
输出模式:token(Token 流)或 ast(AST 树) |
-input |
stdin | 输入 YAML 路径,不指定则从标准输入读取 |
-output |
stdout | 输出路径,不指定则输出到标准输出 |
snakeyaml-ast/
├── snakeyaml.go # 公共 API(Parse / ParseAll / Walk + 类型定义)
├── go.mod # Go 模块定义
├── README.md # 本文档
│
├── cmd/ # 命令行工具
│ ├── print/main.go # 示例:解析 YAML 并遍历 AST
│ └── goyaml-dump/main.go # 工具:导出 Token 流 / AST
│
├── internal/ # 内部实现(不对外暴露)
│ ├── reader/reader.go # 字符流读取器
│ ├── scanner/scanner.go # 词法分析器
│ ├── scanner/constant.go # 扫描器常量
│ ├── token/token.go # Token 类型定义
│ ├── parser/parser.go # 语法分析器
│ ├── event/event.go # Event 类型定义
│ ├── composer/composer.go # AST 组合器
│ ├── node/node.go # 内部节点类型
│ ├── resolver/resolver.go # Tag 解析器
│ └── model/types.go # 测试模型类型
│
├── tests/
│ └── regression_test.go # 回归测试(对比 Java SnakeYAML 黄金文件)
│
├── testdata/ # 测试数据和黄金文件
│ └── golden/ # Java SnakeYAML 生成的参考输出
│
└── tools/
└── oracle-java/ # Java 参考实现(用于生成黄金文件)
本项目的目标是与 Java SnakeYAML 产生完全一致的解析输出。兼容性通过回归测试保证:
- Token 兼容性:205/205 测试文件全部通过 ✅
- AST 兼容性:145/205 测试文件通过 ✅
未通过的 60 个 AST 测试均为 Java 端限制导致(而非 Go 实现的问题):
- Java SnakeYAML 在深度递归结构上触发
StackOverflowError - 部分文件使用了不可解析的自定义 Tag
- ✅ 标量:plain / single-quoted / double-quoted / literal block / folded block
- ✅ 集合:block sequence / flow sequence / block mapping / flow mapping
- ✅ 锚点与别名(
&anchor/*alias) - ✅ 多文档(
---/...) - ✅ 隐式类型解析(int / float / bool / null / timestamp)
- ✅ 显式 Tag(
!!str/!!int/ 自定义 Tag) - ✅ 合并键(
<<) - ✅ Set / OMap / Pairs 等特殊类型
Apache License 2.0