手写 protobuf —— 词法解析与语法解析
前言
Protocol Buffers(Protobuf)作为Google开发的跨语言数据序列化方案,其核心在于通过.proto文件定义数据结构。
proto-qiu是我模仿protobuf的实现,支持解析proto,和java的编解码
本文将深入探讨.proto文件的词法语法分析实现,揭示编译器前端的工作原理。本文基于Go语言实现原型,相关代码在 proto-qiu项目
词法分析器实现解析
词法单元设计
我们定义了5种基础词法单元类型:
type TokenType int
const (
TokenEOF TokenType = iota // 终止符
TokenIdent // 标识符(message/Person/string)
TokenNumber // 数字字面量
TokenString // 字符串字面量
TokenSymbol // 标点符号({}=;)
)
每个词法单元包含类型和原始值:
type Token struct {
Type TokenType // 单元类型
Value string // 原始字符串
}
词法分析器架构
词法分析器采用缓冲读取器实现高效字符处理:
type Lexer struct {
reader *bufio.Reader // 缓冲读取器
pos int // 字符位置追踪
}
// 初始化示例
func NewLexer(r io.Reader) *Lexer {
return &Lexer{reader: bufio.NewReader(r)}
}
核心处理逻辑
空白与注释处理
通过状态机跳过以下内容: • 空格、制表符、换行符 • 单行注释(//) • 多行注释(/* */)
实现关键函数:
func (l *Lexer) skipWhitespace() {
for {
// 处理各种空白和注释
}
}
标识符识别
采用两阶段验证策略:
// 首字符验证
func isIdentStart(r rune) bool {
return unicode.IsLetter(r) || r == '_'
}
// 后续字符验证
func isIdentPart(r rune) bool {
return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '.'
}
特别说明:proto规范中仅包名和类型名允许包含点号(.)
多类型字面量处理
类型 | 处理函数 | 示例 |
---|---|---|
数字 | readNumber() | 123 |
字符串 | readString() | "hello" |
符号 | 直接读取 | {, =, ; |
词法分析示例
输入.proto片段:
message Person {
string name = 1;
}
输出词法单元序列:
TokenIdent("message")
TokenIdent("Person")
TokenSymbol("{")
TokenIdent("string")
TokenIdent("name")
TokenSymbol("=")
TokenNumber("1")
TokenSymbol(";")
语法分析器
语法分析器架构
采用递归下降解析器设计:
type Parser struct {
lexer *Lexer // 词法分析器实例
protoc *Protoc // 最终AST
currentToken Token // 当前处理的词法单元
}
AST核心结构示意:
type Protoc struct {
Syntax string // 语法版本
Package string // 包声明
Imports []string // 导入列表
Messages []*Message // 消息定义
Enums []*Enum // 枚举定义
Services []*Service // 服务定义
}
解析流程
顶层解析
func (p *Parser) Parse() (*Protoc, error) {
for p.currentToken.Type != TokenEOF {
switch p.currentToken.Value {
case "syntax": p.parseSyntax()
case "package": p.parsePackage()
case "import": p.parseImport()
case "message": p.parseMessage()
case "enum": p.parseEnum()
case "service": p.parseService()
}
}
return p.protoc, nil
}
消息解析
支持多层级嵌套定义:
func (p *Parser) parseMessage() (*Message, error) {
msg := &Message{}
// 解析消息名称
// 处理消息体内容:
// - 字段定义
// - 嵌套消息
// - 枚举定义
// - oneof结构
return msg, nil
}
关键特性实现
字段解析
处理多种字段修饰符:
func (p *Parser) parseField() (*Field, error) {
field := &Field{Options: &FieldOptions{}}
// 处理repeated修饰符
if p.currentToken.Value == "repeated" {
field.IsRepeated = true
p.advance()
}
// 解析类型(基础类型或自定义类型)
field.TypeName = p.currentToken.Value
p.advance()
// 解析字段名和编号
field.Name = p.currentToken.Value
p.advance()
p.expect(TokenSymbol, "=")
field.FieldNumber, _ = strconv.Atoi(p.currentToken.Value)
return field, nil
}
Map类型处理
通过语法糖转换为等效消息:
// 输入定义
map<string, int32> scores = 1;
// 转换为
message string_int32_map_entry {
string key = 1;
int32 value = 2;
}
repeated string_int32_map_entry scores = 1;
错误处理机制
实现智能错误定位:
func (p *Parser) expect(t TokenType, values ...string) error {
if p.currentToken.Type != t {
return fmt.Errorf("[Line %d] Expected %v, got %v",
p.lexer.line, t, p.currentToken.Type)
}
// 可选值验证
if len(values) > 0 && !contains(values, p.currentToken.Value) {
return fmt.Errorf("[Line %d] Expected one of %v, got %v",
p.lexer.line, values, p.currentToken.Value)
}
return nil
}