Skip to content

weaweawe01/snakeyaml-ast

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

snakeyaml-ast

Go License

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 — 提供 ParseParseAllWalk 三个核心函数,用法极其简洁
  • 源码位置追踪 — 每个节点都记录了在源文件中的起止位置(行、列、偏移量)

安装

go get github.com/weaweawe01/snakeyaml-ast

快速开始

解析单个文档

package 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
}

遍历 AST

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

API 参考

核心函数

Parse(yaml string) (*Node, error)

解析 YAML 字符串,返回第一个文档的 AST 根节点。如果输入为空或无效,返回错误。

ParseAll(yaml string) ([]*Node, error)

解析包含多个文档的 YAML 字符串(以 --- 分隔),返回每个文档的 AST 根节点切片。

Walk(n *Node, fn func(path string, n *Node))

深度优先遍历 AST 树,对每个节点调用回调函数 fnpath 参数描述节点在树中的位置:

路径格式 含义
root 根节点
root[0] 序列的第 0 个元素
root{0}.key 映射的第 0 对键值对的键
root{0}.value 映射的第 0 对键值对的值

类型定义

Node — AST 节点

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)
}

NodeKind — 节点类型

含义
ScalarNode 标量值(字符串、数字、布尔等)
SequenceNode YAML 序列(列表)
MappingNode YAML 映射(字典)

ScalarStyle — 标量引号样式

YAML 示例
PlainStyle hello
SingleQuotedStyle 'hello'
DoubleQuotedStyle "hello"
LiteralStyle | hello
FoldedStyle > hello

FlowStyle — 集合样式

YAML 示例
FlowAuto 自动(默认块式)
FlowBlock 块式(缩进式)
FlowFlow 流式([a, b]{a: 1}

Mark — 源码位置

type Mark struct {
    Line   int // 行号(0 起始)
    Column int // 列号(0 起始)
    Index  int // 字符偏移量(绝对位置)
}

KeyValue — 映射键值对

type KeyValue struct {
    Key   *Node
    Value *Node
}

Error — 解析错误

type Error struct {
    Message string
}

常量

YAML Tag 常量

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 可序列化的数据结构,用于黄金文件测试比对

CLI 工具

goyaml-dump

命令行工具,支持将 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

支持的 YAML 特性

  • ✅ 标量: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 等特殊类型

License

Apache License 2.0

About

golang snakeyaml AST

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors