用Go复刻GitHub搜索过滤语法

折腾党小雨 中级 11小时前 478 浏览 13 点赞 约 2 分钟

GitHub Issue 那个 is:open label:bug 的搜索框其实是个很典型的解析问题。很多人第一反应是用正则或者 strings.Split 暴力拆分,但只要遇到 label:"help wanted" 这种带空格的引号,或者 created:>2024-01-01 这种比较运算符,手写逻辑立刻就会变成一场灾难。

用Go复刻GitHub搜索过滤语法

我尝试用 participle 这个库来实现一套类似的解析器,它的核心逻辑非常硬核:直接用 Go 的 struct 定义语法,不需要写单独的 .y 语法文件,也不需要代码生成。

避坑:为什么不要手写字符串切分

大多数人写这种过滤逻辑的初版代码大概长这样:

用Go复刻GitHub搜索过滤语法

// 典型的“以后再重构”式写法
parts := strings.Fields(query)
for _, p := range parts {
    if strings.HasPrefix(p, "label:") {
        labels = append(labels, strings.TrimPrefix(p, "label:"))
    } else if strings.HasPrefix(p, "is:") {
        state = strings.TrimPrefix(p, "is:")
    }
    // 后面跟着几十个 else-if,每增加一个维度就多写一遍
}

这种写法在处理简单词时没问题,但一旦涉及否定词(如 -author:bot)或范围查询,正则会堆叠得极其臃肿。把这个问题从“字符串处理”提升到“语法解析”维度,代码量反而会下降。

实战:用 Struct 定义语法树 (AST)

用Go复刻GitHub搜索过滤语法

participle 中,结构体字段的 tag 就定义了语法规则。要实现一个支持限定符(Qualifier)、自由文本和否定词的搜索语言,只需要三个结构体:

// Query 代表整个查询语句:由多个 Term 组成,隐含 AND 关系
type Query struct {
    Terms []*Term `parser:"@@*"`
}

// Term 是最小搜索单元:可以是 key:value 形式的限定符,也可以是纯文本
// @Dash? 处理前缀 "-" 表示否定
type Term struct {
    Negated    bool        `parser:"@Dash?"`
    Qualifier  *Qualifier  `parser:"( @@"`
    Text       *Value      `parser:"| @(String | Ident | Number) )"`
}

![用Go复刻GitHub搜索过滤语法](/uploads/articles/b069f1c29b5013af.jpg)

// Qualifier 处理 "key:value" 格式,例如 is:open 或 created:>2024-01-01
type Qualifier struct {
    Key   string `parser:"@Ident"`
    Colon string `parser:"@Colon"`
    Value string `parser:"@Ident"` 
}

部署与运行效果

通过这种方式,输入字符串会被直接转换为一个强类型的语法树。接下来的步骤就是遍历这个树,将其编译成参数化的 SQL 语句,从而避免 SQL 注入并保证查询效率。

实操运行效果如下:

$ make run q='is:open label:bug'
query: is:open label:bug
6 issue(s)

# STATE AUTHOR CREATED COMMENTS LABELS TITLE
1 open octocat 2024-02-10 12 bug,urgent Login button unresponsive on mobile
6 open bob 2024-04-18 15 bug,p0,urgent Memory leak in worker pool

这种基于 AST 的工作流比正则稳健得多,而且扩展性极强。如果你需要增加 OR 逻辑或者括号优先级,只需要在 Query 结构体中增加递归定义即可。

AI编程AI编程实战beginnerswebdevprogramming

全部回复 (3)

大Jerry 高级 11小时前
之前试过正则,结果遇到带空格的标签直接崩了,还是得写解析器。
0 回复
副业中创业者 初级 11小时前
这种带引号的参数,是用状态机处理的还是直接调了库?
0 回复
远程办公技术宅 中级 11小时前
还得考虑下权重排序,不然搜出来的结果太乱了。
0 回复

发表回复

支持 Markdown 格式