首页 / 资讯中心 / 文章详情

CJASTHelper是如何把AST还原成源码的?ToCangjiePass实现原理深度剖析

CJASTHelper是如何把AST还原成源码的?ToCangjiePass实现原理深度剖析 ★ FEATURED ARTICLE
CJASTHelper是如何把AST还原成源码的ToCangjiePass实现原理深度剖析【免费下载链接】CJASTHelperCangjie Abstract Syntax Tree Helper项目地址: https://gitcode.com/Cangjie-SIG/CJASTHelperCJASTHelper是基于 Cangjie 开源编译器前端的AST 操作与源码转换工具其中ToCangjiePass负责把解析、语义分析后的AST抽象语法树还原成可读的 Cangjie 源码。本文将带你完整看懂它的实现原理访客模式如何遍历 AST、100 多种节点如何逐一打印、以及编译器“解糖”后的代码如何被逆向还原成你熟悉的语法。为什么需要“AST 还原源码”Cangjie 编译器前端处理源码时会经历多个阶段parse → (desugared-parse) → macro → sema → desugared-sema每个阶段的 AST 形态都不同详见 doc/usage.md语法糖被展开如x[i]变成x.[](i)调用属性访问变成 getter/setter 调用构造函数调用变成init()调用CJASTHelper 的--dump-source选项能在任意阶段输出当时的源码方便你观察编译器内部发生了什么。核心引擎就是本文主角——ToCangjiePass 源码还原 Pass。整体架构Pass 插件 访客模式CJASTHelper 采用插件化 Pass 管线架构总览见 doc/design.md。to-cangjie这个 Pass 以动态库cjast_to_source_pass的形式加载注册关系定义在 config/passes.json 中{ group: to-source, names: [to-cangjie, to-java], lib: cjast_to_source_pass, dependencies: [desugar] }类继承结构非常清晰类职责位置PassPass 基类定义Run(AstNode)入口Pass.hToSourcePass持有输出文件流 Printer打印器 访客ToSourcePass.hToCangjiePass真正实现全部节点打印逻辑ToCangjiePass.hConstAstVisitor按节点类型分发 Before/Visit/After 回调ConstAstVisitor.hToSourcePass::Run()就两件事创建输出目录然后Traverse(node, visitor)开始遍历ToSourcePass.h。核心机制一100 节点处理器的批量注册Cangjie 的 AST 有上百种节点声明、类型、模式、表达式……。ToCangjiePass 通过宏EXPAND4批量注册每个节点的Visit处理器例如 ToCangjiePass.cppVarDecl、FuncDecl、ClassDecl等声明节点RefType、OptionType、FuncType等类型节点IfExpr、MatchExpr、LambdaExpr等表达式节点每个Visit函数的套路高度一致打印关键字 → 递归打印子节点 → 补充前缀/后缀。比如IfExpr的还原ToCangjiePass.cpp打印if再打印条件表达式并包裹()打印{缩进后递归打印 then 块再打印}如果有 else 分支打印else { ... }配合Printer的Indent/Unindent能力输出的源码天然带正确缩进。核心机制二Before 钩子拦截“解糖节点”真正精彩的还原逻辑藏在Before钩子里。对 9 种会被解糖的节点MainDecl、AssignExpr、UnaryExpr、BinaryExpr、CallExpr、RefExpr、SubscriptExpr、OptionType、MacroDecl见 ToCangjiePass.hPass 提供了Before重写——它在默认遍历之前执行有机会“劫持”打印过程。以赋值表达式为例ToCangjiePass.cpp节点没有desugarExpr未解糖→ 返回Cont()走正常 Visit 打印有desugarExpr且开启解糖模式 → 直接打印解糖结果返回Skip()有desugarExpr但要求还原源码 → 识别出x.[](i, y)这种“成员函数调用”结构反向还原为x[i] vVisitResult::Skip()是关键它告诉遍历器“这个节点我已处理完别再走默认逻辑了”。这就是逆向解糖的实现骨架。核心机制三语义级还原sema 阶段专属语义分析后的 AST 丢失了大量原始语法信息还原难度最大。ToCangjiePass 用三个“尝试”函数链解决ToCangjiePass.cpp还原场景解糖/语义后形态还原为实现函数构造函数调用init(args)调用A(args)TryPrintInitCall属性读写x.$aget()/x.$aset(v)x.a/x.a vTryRecoverPropCallExpr运算符重载a.(b)/a.[](x)a b/a[x]TryRecoverOverloadCallExpr其中运算符还原还会贴心地保留一条/* Desugared ... */注释记录解糖后的原始形态方便对照。更复杂的for-in循环还原则展示了“模板匹配”功力编译器把for (x in start..stop:step)解糖成var $iter-i ...while循环的四条语句块Pass 断言块结构后重新拼装回for-in形态ToCangjiePass.cpp。还有个精巧的辅助设施——解糖变量名字表desugaredVarId编译器生成的$iter-compiler这类临时变量在还原时会被规范化去$、-转_、重名加序号并记录“原节点 → 新名字”的映射后续所有引用处通过查表统一替换ToCangjiePass.cpp。配置驱动Desugar 与 Sema 双开关所有还原行为由 PassConfig 的两个标志位控制Desugar 模式打印“解糖后”的源码看编译器内部形态Sema 模式基于语义信息补齐类型标注、泛型实参、丢失的修饰符/注解--dump-sourceparse与--dump-sourcedesugared-sema的区别本质就是这两个开关的组合。当 sema 阶段节点丢失了显式类型时Pass 会用语义类型Ty兜底补全ToCangjiePass.cpp。上手体验一条命令还原源码构建完成后Linux/macOS 用bash build.sh -t Release -b最常用的命令mkdir -p out ./build/bin/cjah --dump-sourcedesugared-sema --output-typedylib main.cj --output-dir out # 产物out/main_source.cj还可以叠加--filter-declsfunc,class只还原顶层函数和类。完整选项见 doc/usage.md。总结ToCangjiePass 的实现可以浓缩为三层设计遍历层ConstAstVisitor按AstKind分发宏批量注册 100 节点处理器打印层Printer负责缩进与前缀/后缀包裹让还原结果天然可读还原层Before钩子 Skip拦截 模板匹配把解糖/语义化后的节点逆向还原为原始语法这种“访客 钩子 模式匹配”的组合是编译器工具中 AST→源码 反序列化问题的经典解法对想实现类似 unparser 的同学很有参考价值。 相关源码入口src/passes/to_source/还原 Pass 全实现、src/passes/desugar/解糖 Pass、test/test_ci.cpp管线回归测试。【免费下载链接】CJASTHelperCangjie Abstract Syntax Tree Helper项目地址: https://gitcode.com/Cangjie-SIG/CJASTHelper创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站