十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

PostHog HogQL 解析器:基于 ANTLR 的语法生成流程与 C++/WASM 双端解析器构建管线

PostHog HogQL 解析器:基于 ANTLR 的语法生成流程与 C++/WASM 双端解析器构建管线 PostHog HogQL 解析器基于 ANTLR 的语法生成流程与 C/WASM 双端解析器构建管线【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog本文围绕 PostHog 仓库中 HogQL 语法目录下的说明文档展开完整讲解如何从 ANTLR 语法文件HogQLParser.g4与拆分式 Lexer 文件重新生成 C 解析器源码包括 ANTLR 工具在 macOS/Ubuntu 上的安装方式、pnpm run grammar:build背后的确切命令行拼接逻辑、生成产物的落盘位置以及 CI 如何强制保证语法文件与生成代码不漂移。读完后你可以独立完成一次 HogQL 语法的修改与再生成并理解这套语法如何同时支撑 Python 原生扩展和 WebAssembly 两个发布包。语法文件构成一个 Parser 加一个拼接式LexerHogQL 是 PostHog 的查询语言其解析能力由 common/hogql_parser 目录封装为两个发布包PyPI 上的hogql_parserCPython 原生 C 扩展和 npm 上的posthog/hogql-parserWebAssembly 模块。两者共享同一套 C 解析器核心与 ANTLR4 语法因此产出完全一致的 AST见 common/hogql_parser/README.md 的说明。语法定义位于 posthog/hogql/grammar/ 目录共三个.g4文件文件角色HogQLParser.g4解析器语法parser grammar声明所有语法规则HogQLLexer.common.g4词法器lexer的共享规则关键字、token、通用规则HogQLLexer.cpp.g4C 目标专用的 Lexer 头部lexer grammar HogQLLexer声明、header/members中的 C 自定义代码按 README 的说法构建时会将HogQLLexer.common.g4共享规则与目标特定的HogQLLexer.cpp.g4拼接成最终喂给 ANTLR 的词法器文件。之所以要拆分从HogQLLexer.common.g4顶部的注释可以确认NB! We cat either HogQLLexter.cpp.g4 or HogQLLexter.python.g4 when generating the grammar.即同一份共享 Lexer 规则可以被不同目标的后缀文件拼接复用而当前仓库中 C 是唯一的 ANTLR 生成目标——Rust 端的解析器是手写的不消费这些.g4文件只是手工镜像它们的语法规则见 rust/hogql/ 目录README 中已明确此点。这也是修改语法时需要注意的一处双份维护成本。Parser 文件开头声明了与 Lexer 的绑定关系HogQLParser.g4parser grammar HogQLParser; options { tokenVocab HogQLLexer; }安装 ANTLRmacOS 与 Ubuntu 两种方式生成源码的前提是本机装有antlr可执行文件且版本必须是 4.13.2。README 给出两条安装路径macOSHomebrew 一键安装brew install antlr注意如果 Homebrew 安装的版本比 4.13.2 更新需要同步修改 .github/workflows/ci-hog.yml 中ANTLR_VERSION的值否则本地再生成结果与 CI 校验结果不一致PR 会卡在生成代码未提交这一步原因见下文 CI 一节。Ubuntubash手动下载 ANTLR 发行包README 提供的完整脚本如下它下载 ANTLR 的 complete jar并生成一个名为antlr的 bash 包装脚本export ANTLR_VERSION4.13.2 sudo apt-get install default-jre mkdir antlr cd antlr curl -o antlr.jar https://www.antlr.org/download/antlr-$ANTLR_VERSION-complete.jar export PWDpwd echo #!/bin/bash antlr echo java -jar $PWD/antlr.jar \$* antlr chmod x antlr export CLASSPATH.:$PWD/antlr.jar:$CLASSPATH export PATH$PWD:$PATH执行后antlr命令即出现在当前 shell 的 PATH 中底层等价于java -jar antlr.jar。这套手工搭 jar 包装脚本的做法与 CI 中的流程完全一致下文会看到 CI 也是这么做的因此本地与 CI 的行为可以保持对齐。pnpm run grammar:build一次调用背后的完整命令链安装好 ANTLR 后在仓库根目录执行pnpm run grammar:build这一条命令会把 C 解析器重新生成到common/hogql_parser/。它真正包装的是根目录 package.json 中的grammar:build:cpp脚本grammar:build仅是其别名cd posthog/hogql/grammar \ cat HogQLLexer.cpp.g4 HogQLLexer.g4 \ tail -n 2 HogQLLexer.common.g4 HogQLLexer.g4 \ antlr -o ../../../common/hogql_parser -DlanguageCpp HogQLLexer.g4 \ rm HogQLLexer.g4 \ antlr -o ../../../common/hogql_parser -visitor -no-listener -DlanguageCpp HogQLParser.g4可以把它拆解成五步理解拼接 Lexer 文件cat HogQLLexer.cpp.g4 HogQLLexer.g4以 C 专用头含lexer grammar HogQLLexer;声明与header/members自定义 C 代码作为新文件开头随后tail -n 2 HogQLLexer.common.g4 HogQLLexer.g4把共享规则文件跳过第 1 行追加进来——第 1 行正是 common 文件里那句会重复的lexer grammar HogQLLexer;声明跳过它才能拼出合法的单 grammar 文件。生成词法器antlr -o ../../../common/hogql_parser -DlanguageCpp HogQLLexer.g4输出HogQLLexer.cpp/.h、.tokens、.interp等到common/hogql_parser/。清理临时文件rm HogQLLexer.g4删除拼接产物保持语法目录干净仓库里只保留三个源文件见 posthog/hogql/grammar/。生成解析器对HogQLParser.g4再次调用 ANTLR参数-visitor -no-listener表示只生成 Visitor 接口HogQLParserVisitor而不生成 Listener 版本。落盘位置所有产物统一进入common/hogql_parser/与手写的桥接代码放在一起。在 common/hogql_parser/ 目录中可以看到两类文件ANTLR 生成物HogQLLexer.cpp、HogQLParser.cpp、HogQLParserBaseVisitor.cpp、HogQLParserVisitor.cpp、.interp/.tokens以及手写胶水层parser_python.cpp供 Python 扩展调用、parser_wasm.cpp供 Emscripten 导出、parser_json.cpp负责把 AST 序列化为 JSON、index.cjs/index.d.ts供 npm 包使用。package.json 中的build脚本通过emcmake cmake把这套 C 代码编译成 WASM 放入dist/而 Python 侧则由 pyproject.toml 用 scikit-build 流程构建原生扩展——两个运行时、一套语法核心。CI 如何强制语法文件与生成代码零漂移再生成出来的 C 文件必须提交进仓库否则 CI 会直接失败。.github/workflows/ci-hog.yml 中的 Check if ANTLR definitions are up to date 步骤做了三件事在 CI 中复现本地安装流程下载antlr-4.13.2-complete.jar主源失败时回落到 Maven 镜像生成同样的antlr包装脚本执行npm run grammar:build即上文grammar:build:cpp全量脚本重新生成git diff --exit-code——只要仓库中提交的生成文件与本次再生成结果有任何差异CI 立即报错。版本锁定也在这一步里env中写死ANTLR_VERSION: 4.13.2注释说明这与 2024 年 8 月 Homebrew 提供的版本一致apt 源中的 ANTLR 版本过旧不可用并要求common/hogql_parser/pyproject.toml中保持相应版本配套。从 pyproject.toml 实际内容看构建 wheel 时下载的是antlr4-cpp-runtime-4.13.1-source.zip并校验 md5随后 cmake 编译 C 运行时并安装到系统目录——也就是说词法/语法生成ANTLR 4.13.2与运行时 C 库的构建是两条独立但版本配套的管线。由此得出实操约束任何一次对.g4文件的改动都必须本地跑一遍pnpm run grammar:build把common/hogql_parser/下的 diff 一并提交且本地 ANTLR 版本必须与 CI 的 4.13.2 对齐避免生成代码的版本性差异导致 CI 误判漂移。语法本体速览从 README 的三个设计点回到.g4源码README 末尾用三句话概括了这套语法与 ClickHouse 官方 ANTLR 语法ClickHouseParser.g4ClickHouse 仓库自带的差异。把这三点放回 HogQLParser.g4 源码中可以得到更具体的印证1. 只保留 SELECT 语句。整个 parser grammar 的顶层入口只有三类HogQLParser.g4program: declaration* EOF; // Hog 程序变量声明与语句 expr: columnExpr EOF; // 单个表达式 select: (selectSetStmt | selectStmt | hogqlxTagElement) SEMICOLON? EOF; // SELECT 查询没有 INSERT/UPDATE/DROP 等写语句规则。select入口的三选一中还混入了hogqlxTagElement形如Chart ...的模板标签对应hogqlxChildElement/hogqlxTagElement规则族HogQLParser.g4让查询文本里能嵌入带属性的标签元素。2. 未实现的 ClickHouse 特性会被拒绝。README 用ever changing list, check the code指向代码层面的校验被解析出的 AST 后续要在 Python 侧的 resolver/编译器中过一遍不支持的语法如某些 ClickHouse SQL 特性在实现层抛出错误。语法层本身偏宽松、实现层收紧是这类SQL 子集查询语言常见的分层策略。3. 支持{val1}形式的占位符。语法中的定义是HogQLParser.g4placeholder: LBRACE columnExpr RBRACE;{ ... }内是一个完整的columnExpr可以出现在列引用columnIdentifier的可选分支、表表达式TableExprPlaceholder与采样比例ratioExpr等位置。README 里的例子team_id {val1}即走这条规则。占位符的下游处理在 Python 侧posthog/hogql/placeholders.py 中的find_placeholders/visit_placeholder会把 AST 中的Placeholder节点拆成简单字段占位符与复杂表达式占位符两类后者会被送进 Hog 虚拟机求值——这也解释了为什么HogQLParser.g4里同时存在programHog 程序入口。值得留意的几处语法设计细节两级优先级的表达式分层columnExpr布尔与/或、三元、别名层与columnExprValue算术、比较、函数调用、主叶节点层的拆分在 HogQLParser.g4 有长注释解释——这是 ANTLR4 左递归规则下表达BETWEEN ... AND ... AND ...正确分组的唯一方式注释明确说拆成两条规则the only way to express this in ANTLR4。修改表达式规则时务必读懂这段注释否则会破坏既有优先级。无限深度的属性链nestedIdentifier: identifier (DOT identifier)*允许properties.b.a.a.w.a.s这类任意深度嵌套引用HogQLParser.g4 的注释指出这与 ClickHouse SQL 不同解析后会被折叠成单个Field节点chain[...]。模板字符串templateString/fullTemplateString规则HogQLParser.g4用QUOTE_SINGLE_TEMPLATE等 token 支持f...风格内嵌{表达式}的字符串与 npm 包 API 中的parseFullTemplateString相对应。Lexer 内嵌 C 自定义代码HogQLLexer.cpp.g4的members块包含真实 C 实现——skipWsAndComments同时跳过//、--、#三种行注释并特意用 ASCII 范围判断规避std::isalpha对非 ASCII 输入的未定义行为isOpeningTag则解决既是小于运算符又是 HogQLx 标签起始符的歧义。这意味着 C 目标的词法行为并非纯 ANTLR 声明式改动 Lexer 时不能只看规则本身。新增关键字的提醒HogQLLexer.common.g4顶部注释写着dont forget to add new keywords to the parser rule keyword!——对应 parser 侧的keyword规则HogQLParser.g4它是标识符可用作identifier的白名单来源。只加 Lexer token 而漏掉 parser 侧keyword规则新关键字将无法作为identifier出现是修改语法时最容易踩的坑之一。适用前提与小结流程适用前提仓库根目录有 pnpm 环境且本机 ANTLR 版本为 4.13.2与 ci-hog.yml 一致生成是全量覆盖common/hogql_parser/中 ANTLR 产物手写文件parser_*.cpp、json.cpp等不受影响但二者必须能一起通过 cmake 编译npm run build走 Emscriptenpip install ./common/hogql_parser走 Python 扩展构建Rust 解析器rust/hogql/不走 ANTLR语法规则变化需在其内部手工同步这是 README 明确声明的设计决策提交前务必确认git status中common/hogql_parser/下的 diff 已包含在变更内因为 CI 会用git diff --exit-code对再生成结果做严格校验。整条链路可以概括为三个.g4源文件语法目录→ 拼接 两次 ANTLR 调用pnpm run grammar:build→ C 产物common/hogql_parser/→ 两条构建出口PyPI 原生扩展 / npm WASM→ CI 零漂移校验ci-hog.yml。掌握这条管线后无论是给 HogQL 加一个新关键字、调整运算符优先级还是排查为什么我改了语法 PR 却红了都有了明确的排查路径。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表