手写 protobuf —— 词法解析与语法解析

136 阅读3分钟

手写 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
}