codegraph 的 COBOL 语法改造:用 tree-sitter-cobol 补丁解析真实主机与 GnuCOBOL 代码
2026/9/7 18:51:45 网站建设 项目流程

codegraph 的 COBOL 语法改造:用 tree-sitter-cobol 补丁解析真实主机与 GnuCOBOL 代码

【免费下载链接】codegraphPre-indexed code knowledge graph, auto syncs on code changes, for Claude Code, Codex, Gemini, Cursor, OpenCode, AntiGravity, Kiro, CoPilot, and Hermes Agent — fewer tokens, fewer tool calls, 100% local项目地址: https://gitcode.com/GitHub_Trending/co0degr/codegraph

本文以 codegraph 仓库内docs/grammars/tree-sitter-cobol.md为核心,完整讲解 vendored 的 COBOL tree-sitter 语法是如何在上游基础上打补丁的:10 项补丁各自修复了哪些真实生产代码中的语法盲区,补丁前后的实测解析健康度数据,以及如何一步步重建出tree-sitter-cobol.wasm。读完本文,你可以理解 codegraph 为什么必须改造上游 COBOL 语法才能索引 CICS/DB2/GnuCOBOL 代码,并能按文档中的步骤在本地复现并重建这个 wasm 二进制。

补丁的来源与动机

codegraph 仓库中使用的 COBOL 语法文件 tree-sitter-cobol.wasm 是从上游 yutaro-sakamoto/tree-sitter-cobol 这份补丁(改动集中在grammar.jssrc/scanner.c两个文件,其余产物全部由tree-sitter generate重新生成)。

上游语法的目标是 COBOL85,派生自 opensource-cobol 项目,并通过过 NIST 测试集,因此它解析"教科书式"的批处理 COBOL 没有问题。但文档指出,它恰恰卡死在真实 mainframe 和 GnuCOBOL 代码中占主导地位的构造上——补丁正是为了解决这些盲区。

补丁新增/修复的 10 类语法

以下 10 项完整对应原文档 "What the patch adds" 一节,并补充补丁文件中的具体实现位置。

1.EXEC ... END-EXEC块(CICS / SQL / DLI)

上游完全没有 EXEC 支持——EXEC SQLEXEC CICS内部的 token 会被前面的语句持续吸收,直到某个 token 把句法打断,级联效应是把数百行代码坍缩成一个大 ERROR。补丁在外部扫描器中新增了一个EXEC_BLOCKtoken:扫描器一旦匹配到单词EXEC(大小写不敏感)后跟空白,就用一个 8 字节滑窗向后消费直到END-EXEC,把整个块作为一个不透明的单节点发出。补丁后的语法把它暴露为exec_statement,既可作为过程语句(procedure statement),也可作为数据分区的条目(用于EXEC SQL INCLUDE/DECLARE场景)。这一点在补丁的src/scanner.c部分中可见:EXEC_BLOCK加入TokenType枚举,tree_sitter_COBOL_external_scanner_scan中新增了按字符滚动匹配end-exec的滑窗逻辑。

2. 不带空格的NOT=

IBM COBOL 接受NOT=这种写法。上游的_NOT_EQUALtoken 只匹配!=NOT [空格] (EQUAL|=),补丁把正则改为(!=)|([nN][oO]tT),使NOT=无需空格也能被识别。

3.FD <name>.后直接跟COPY

真实代码里文件描述条目常常只有FD MYFILE.一行,记录布局由后面的COPY提供。上游要求每个 FD 之后必须有字面的记录描述(record description),导致解析失败。补丁把file_descriptionlinkage_section中的record_description_list从必选改为optional(...),同时允许record_description_list内部穿插exec_statement

4. 多对替换的COPY ... REPLACING ==pseudo-text== BY ==pseudo-text==

上游的replacing_clause存在两个问题:从不消费REPLACING关键字本身,且只允许一对 WORD/字符串替换。补丁重写为replacing_clause: $ => seq($._REPLACING, repeat1($.replacing_pair)),并新增pseudo_texttoken(正则/==([^=\n]|=[^=\n])*==/)支持==占位符==形式的伪文本替换对,数量不再受限。

5. 单引号字符串的续行

固定格式 COBOL 用第 7 列的连字符表示续行。上游扫描器只对双引号字符串实现了续行;补丁把引号字符泛化为扫描时实际遇到的引号("'),并额外处理了双写引号转义('DON''T'中相邻两个'是转义而非闭合)。补丁同时把字符串消费时的右边界从硬编码的 72 列改为可变宽度(见第 8 项 wide mode)。

6.FUNCTION <intrinsic>(refmod)

上游的泛型 FUNCTION 回退规则能接受参数但不接受引用修饰后缀(reference modification),例如FUNCTION CURRENT-DATE(1:4)会解析失败——那些专用内建函数 token(如CURRENT-DATE-FUNC)只匹配 opensource-cobol 预处理后的名称。补丁把泛型回退规则改为seq($.WORD, optional($.func_args), optional($.func_refmod)),补上了func_refmod这一段。

7. 独立 copybook 入口点

.cpy文件(copybook)没有IDENTIFICATION DIVISION,上游的start规则只接受program_definition,所以独立 copybook 无法解析。补丁把start改为optional(choice(repeat1($.program_definition), $.copybook_fragment)),并新增copybook_fragment规则,其内容是record_description_list或过程分区内容的二选一——"一个文件要么是完整程序,要么是一个 fragment",从而保持程序后缀的无歧义。

8. 自由格式源码的 wide mode

这是补丁中最巧妙的一项,也是 codegraph 提取器与扫描器协同设计的核心:

  • 提取器侧:codegraph 的 COBOL 提取器在解析前(preParse)把自由格式 COBOL 转成固定格式——每行左侧补 7 个空格,并在第一行的序列区(第 1–6 列)写入CGWIDE哨兵
  • 扫描器侧:外部扫描器在消费第 1 行序列区时逐字节比对CGWIDE六个字符,匹配成功就把扫描器状态置 1,随后把固定格式的右边界(第 72 列)放宽到 4096 列(CG_FIXED_WIDTH 72/CG_WIDE_WIDTH 4096)——自由格式的行经常超过 72 列,不放宽会截断字符串和语句;
  • 状态持久化:这个 1 字节的标志位通过外部扫描器的serialize/deserialize接口携带,保证 tree-sitter 的增量解析不会丢失 wide mode 状态;
  • 兼容性:固定格式文件不写哨兵,行为与上游完全一致,不受影响。

在 codegraph 源码中可以看到这段预处理的具体实现:src/extraction/languages/cobol.ts 的preParse钩子先探测自由格式(division 头、PROGRAM-ID或 level number 行出现在第 8 列之前),命中后逐行左移 7 列并在首行插入CGWIDE;行号保持不变,列号漂移 7 列,对行导向的消费者可以接受。

9. COBOL-2002 / GnuCOBOL 表面语法

补丁一次性补齐了一批现代 COBOL 写法:

构造补丁中的体现
BINARY-LONG-LONGFLOAT-LONGFLOAT-SHORT用法新增_BINARY_LONG_LONG/_FLOAT_LONG/_FLOAT_SHORT大小写不敏感正则及对应 exposed token,并加入usage规则的 choice
PROGRAM-ID. X IS RECURSIVE.新增is_recursive规则,并入program_name尾部选项
关系型WHEN对象(WHEN > 0_evaluate_object新增prec.right(seq(choice('=', '>', '<', '>=', '<=', $._NOT_EQUAL), $.expr))
缩略组合关系(WHEN X < 0 OR > Yexpr规则新增seq($.expr, choice($.AND, $.OR), $._comparator, $._expr_calc)——第二比较的主体被隐含。补丁注释说明:语法里原有的AND_LT/OR_GT组合 token 永远打不过关键字词法分析,所以必须显式建模
位运算符B-AND/B-OR/B-XOR/B-NOT/B-SHIFT-L(-LC)/B-SHIFT-R(-RC)新增B_AND等 6 个 token 正则,进入_expr_calc_binary/_expr_calc_unary
FREE语句新增free_statement规则,加入_statementchoice
VALUE <constant-name>value_item的开头从$._literal改为choice($._literal, $.qualified_word)
PIC X(CONSTANT-NAME)picture_x正则从(\([0-9]+\))?扩展为(\(([0-9]+|[a-zA-Z][a-zA-Z0-9-]*)\))?
>>编译指令作为注释新增directive: $ => />>[^\n]*/并放入extras(GnuCOBOL 的>>SOURCE>>IF>>DEFINE等)
ENTRY 'literal' USING ...(IMS 批处理替代入口点)新增entry_statement规则,_statement中加入
DATE-COMPILED./DATE-WRITTEN./SECURITY.三个 section 规则的comment_entryrepeat1改为repeat,允许零个注释条目

10.CALL ... GIVING

上游本意是支持GIVING的,但一个嵌套位置写错的field()调用把RETURNINGGIVING两个选项整体吞掉了(它们在源码里被包进了同一个optional(choice(...))内部,导致语法生成时该分支失效)。补丁把结构修正为optional(choice(field('returning', ...), field('giving', ...)))

实测解析健康度(vendoring 时测量)

原文档给出了补丁落地时的实测数据,这是评估"为什么要打补丁"的最直接证据:

语料库干净解析数
AWS CardDemo 全部程序,含 DB2/IMS/MQ 变体(44 个.cbl43/44——上游在同一基础集上仅 9/31。唯一残余项是段落之间一条无句点的EXEC SQL INCLUDE,由提取器的preParse修复(见下文)
AWS CardDemo copybook(29 个.cpy28/29——上游为 0/29(失败原因是COPY REPLACING模板内含(TESTVAR1)占位符,替换前本来就不是合法 COBOL)
NIST COBOL85 套件(382 个程序)373/382——与上游持平,无回归
CobolCraft(17 个自由格式 GnuCOBOL 程序,经 wide mode 处理)17/17——上游完全无法解析(自由格式)
上游自身的语料库测试保留 1 个既有失败(comment),无新增失败

其中 AWS CardDemo 的残余项(43/44 中的 1 个)在 codegraph 中有专门的工程化处理:DB2 惯例把EXEC SQL INCLUDE member END-EXEC写成不带句点、夹在两个段落之间的形式,这会绊倒语法的句子机制。提取器的 terminateSqlIncludes 在preParse阶段给这种单行形式的END-EXEC后补一个句点(写入其后本为空格的那个字符位,或在行尾追加),且对其它所有字符保持偏移不变,不影响行号映射。

codegraph 如何消费这套补丁后的语法

补丁让解析"能过",codegraph 的提取层则让解析结果"有用"。以下实现细节来自仓库源码,用于佐证文档中各项补丁的实际用途:

  • 文件类型映射:src/extraction/grammars.ts 把.cbl.cob.cobol.cpy四种扩展名统一映射到cobol语言,加载tree-sitter-cobol.wasm;COBOL 属于 vendored wasm 语言集合,与 Rust kernel 侧的 grammar parity 校验一起维护版本对齐。
  • EXEC 块的二次挖掘:补丁让EXEC ... END-EXEC成为单个不透明节点后,提取器在 handleExec 中对这个不透明节点做文本级挖掘,只提取可静态解析的形状:EXEC CICS LINK/XCTL PROGRAM('X')产生跨程序calls边;EXEC CICS RETURN/START TRANSID发出带cics-transid:前缀的引用(供框架解析器消费);EXEC SQL INCLUDE产生import节点加imports引用(DB2 版的 COPY)。
  • CICS 事务跳转的框架级解析cics-transid:XXXX引用由 src/resolution/frameworks/cics.ts 解析——CICS 的 CSD 配置从不出现在代码仓库中,但按近乎普遍的惯例,每个程序会把自有的事务 id 声明为工作存储常量(05 WS-TRANID PIC X(04) VALUE 'CB00'.)。解析器扫描所有名字含TRAN的数据项的VALUE字面量,把事务 id 映射回声明它的程序模块;无匹配则保持未解析,而不是猜测。
  • copybook fragment 的消费:cobol.ts 的visitNodecopybook_fragment分两条路:含record_description_list时走数据条目遍历(产出variable/field/constant节点),否则按程序段落走过程遍历(产出function节点)——对应补丁第 7 项"数据 copybook 给出记录结构,过程 copybook 给出段落"。
  • 保守的动态分发策略:文档式的补丁只负责让语法解析通过;提取层对无法静态确定的目标(如CALL>git clone https://github.com/yutaro-sakamoto/tree-sitter-cobol cd tree-sitter-cobol git checkout e99dbdc3d800d5fa2796476efd60af91f6b43d93 git apply path/to/tree-sitter-cobol.patch # tree-sitter 0.24.x 需要 tree-sitter.json;语法名必须保持 COBOL # (C 符号是 tree_sitter_COBOL*): cat > tree-sitter.json <<'JSON' { "grammars": [ { "name": "COBOL", "camelcase": "COBOL", "scope": "source.cobol", "path": ".", "file-types": ["cbl", "cob", "cpy"] } ], "metadata": { "version": "0.1.1", "license": "MIT", "description": "COBOL grammar for tree-sitter", "links": { "repository": "https://github.com/yutaro-sakamoto/tree-sitter-cobol" } } } JSON npm install tree-sitter-cli@0.24.5 npx tree-sitter generate npx tree-sitter build --wasm -o tree-sitter-cobol.wasm # 需要 emscripten 或 Docker

    两个关键约束值得注意:

    1. 必须固定在 commite99dbdc3上打补丁——补丁是针对该提交的grammar.js/scanner.c编写的,上游若合并补丁前随意切换 commit,git apply可能失败;
    2. 语法名必须保持COBOL——生成的 C 符号是tree_sitter_COBOL*,wasm 的导出符号名由此确定,改名会导致加载失败。

    npx tree-sitter generate之后,src/parser.c等产物全部再生成,补丁本身只手写两处:grammar.js的语法规则和src/scanner.c的外部扫描器逻辑(EXEC_BLOCKtoken、CGWIDE 哨兵检测、引号泛化的多行字符串)。

    上游化(Upstreaming)与可移植性设计

    文档明确说明:补丁按"可直接上游"的方式编写——每一项都相互独立,且各自附带了导致失败的构造文档,便于单独评审。这些改动已经作为 PR(上游仓库的 pull request #41,分支real-world-cobol-sources)提交给 yutaro-sakamoto/tree-sitter-cobol。PR 描述中列出的实测结论与上文健康度表格一致:AWS CardDemo 从 9/31 提升到 43/44,CobolCraft 从 0 到 17/17,NIST COBOL85 持平于 373/382,上游语料库测试维持唯一一个既有失败(comment)。

    如果上游合并该 PR,codegraph vendored 的 wasm 就可以改为跟随上游版本发布,不再依赖这份补丁;在那之前,在上游 commite99dbdc3上执行git apply tree-sitter-cobol.patch可以逐字节复现当前 fork。这也解释了文档为何把补丁文件(tree-sitter-cobol.patch)与说明文档并置在docs/grammars/下:补丁是"事实来源",wasm 是"构建产物",两者必须可互推。

    小结:一个补丁如何打通"解析→索引→解析引用"整条链路

    回到 codegraph 的项目定位——为 AI 编码代理预索引代码知识图,减少 token 与工具调用。对 COBOL 这种"语法即布局"的列导向语言,仅仅能用上游语法解析教科书代码毫无意义:真实仓库里占主导的 EXEC 块、COPY 替换、自由格式和 COBOL-2002 语法恰恰是上游的盲区。这份补丁的意义在于它打通了三层链路:

    1. 解析层(scanner.c + grammar.js 补丁):让tree-sitter parse对生产代码产出干净 AST,43/44 与 17/17 就是这一层的验收指标;
    2. 提取层(cobol.ts):把 flat 的、列导向的 AST 还原为有范围的程序/段落/字段节点,把 EXEC 不透明块二次挖掘为 CICS/SQL 调用边;
    3. 引用解析层(cics.ts):用仓库内普遍存在的TRAN* VALUE 'XXXX'惯例补上 CSD 缺失的事务映射,把RETURN TRANSID解析到真正的目标程序。

    三层中任何一层缺失,索引出的 COBOL 代码图都只是空壳——而这份文档记录的,正是第一层如何被工程化地补齐,以及它如何与后两层精确咬合。

    【免费下载链接】codegraphPre-indexed code knowledge graph, auto syncs on code changes, for Claude Code, Codex, Gemini, Cursor, OpenCode, AntiGravity, Kiro, CoPilot, and Hermes Agent — fewer tokens, fewer tool calls, 100% local项目地址: https://gitcode.com/GitHub_Trending/co0degr/codegraph

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询