ARTICLE DETAIL

资讯详情

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

Penrose Substance 程序合成器(Synthesizer)源码深度解析:从 SynthesizerSettings 配置到 AST 变异引擎

Penrose Substance 程序合成器(Synthesizer)源码深度解析:从 SynthesizerSettings 配置到 AST 变异引擎 开发工具数据可视化【免费下载链接】penroseCreate beautiful diagrams just by typing notation in plain text.项目地址https://gitcode.com/gh_mirrors/pe/penrose点击查看免费下载本文以仓库中 synthesis 模块文档 为核心骨架结合Synthesizer.ts、Mutation.ts、Search.ts及SubstanceAnalysis.ts的源码实现系统讲解 Penrose 中用于自动生成 Substance 程序变体的核心模块Synthesizer类如何维护合成上下文、如何按SynthesizerSetting配置驱动「增 / 删 / 改」三类 AST 变异操作、如何通过纯函数分析层保证生成程序的类型合法以及基于 AST 差分与观测等价性搜索的进阶能力。读完本文你将能够理解并复现penrose/edgeworth中一键生成多张变体图的核心调用链并能独立编写一份完整的SynthesizerSetting配置。一、模块定位synthesis是什么synthesis是 Penrose 仓库中负责Substance 程序自动合成的模块位于packages/edgeworth/src/synthesis/。它对外导出两个核心构件Synthesizer类包含 Penrose 合成器的核心功能。上层包如penrose/synthesizer以及 Edgeworth 的 UI 层都依赖它来批量生成变异后的 Substance 程序。SynthesizerSettings接口源码中实际命名为SynthesizerSetting见 Synthesizer.ts初始化合成器所必需的配置对象控制变异数量、参数来源、各类语句的权重与开关。模块内共有四个源码文件与两个测试文件职责划分非常清晰文件职责Synthesizer.ts合成器主类、SynthesisContext上下文、SynthesizerSetting配置类型、语句生成器Mutation.ts变异mutation的类型定义、守卫函数check*、执行函数execute*与枚举器enum*Search.ts两个 Substance 程序之间的 AST 差分diff、变异路径搜索、带观测等价性的枚举式搜索SubstanceAnalysis.ts在../analysis/一组纯函数AST 节点的创建、转换、比较、删除、签名匹配等二、Synthesizer的工作原理类与上下文的职责分离README 中用一个两层的对象模型概括了合成器的核心机制2.1Synthesizer变异操作的生产者Synthesizer是合成过程的外层对象它负责产生要做什么变异的数据。所谓变异数据就是一条增 / 删 / 改语句statement的操作描述。它内部维护的字段见 Synthesizer.tsexport class Synthesizer { domEnv: DomainEnv; // 编译后的 Domain 环境类型/谓词/函数/构造子声明 subEnv: SubstanceEnv; // 编译后的 Substance 环境 template: SubProgA; // 模板 Substance 程序若无模板则为空程序 setting: SynthesizerSetting; currentProg: SubProgA; // 当前正在生成的 Substance AST currentMutations: Mutation[]; // 当前程序的变异操作记录 rng: seedrandom.prng; // 可复现的随机数生成器 private choice: T(array: ArrayT) T; // 均匀随机选择 private weightedChoice: T(array: ArrayT) T; // 带权随机选择用于 opWeights private random: RandomFunction; // [min,max] 区间随机整数 }构造函数接收(domEnv, subEnv, setting, subRes?, seed synthesizerSeed)。其中subRes?: [SubstanceEnv, DomainEnv]用于传入模板 Substance 程序如果提供了模板template会被初始化为该程序的深拷贝否则模板是一个空的SubProg。seed通过seedrandom驱动choice、weightedChoice、random三个随机函数因此同一 seed 下生成的变体序列是完全可复现的。2.2SynthesisContextAST 变更的执行者与记账员SynthesisContextSynthesizer.ts是内层对象它在合成单个Substance 程序期间维护全部中间状态export interface SynthesisContext { names: im.Mapstring, number; // 名称 - 出现次数 declaredIDs: im.Mapstring, IdentifierA[]; // 类型名 - 已声明的 ID 列表 generatedNames: im.Mapstring, number; // 自动生成 ID 的前缀 - 最新下标 argOption: ArgOption; // existing | generated | mixed argReuse: ArgReuse; // distinct | repeated subEnv: SubstanceEnv; domEnv: DomainEnv; choice: T(array: ArrayT) T; // 从数组中随机选一个 }上下文是合成过程中的活账本declaredIDs按类型登记所有已声明的 IDgeneratedNames记录自动生成 ID如a0、b1的下标进度。每当Synthesizer决定添加/删除一个Decl语句时Mutation.ts中的appendStmtCtx/removeStmtCtx会同步调用addID/removeIDSynthesizer.ts来更新declaredIDs保证程序语句与上下文登记始终一致——这正是 README 所说SynthesisContext执行实际操作并做必要记账如记录变异操作的含义。initContextSynthesizer.ts会以 seed 创建新的seedrandom.prng并建立上下文同时把subEnv.objIds中已有的对象按类型预登记进declaredIDs。2.3 关键不变量每个程序生成后必须 resetREADME 特别强调Synthesizer每次生成一个新的 Substance 程序时都会显式reset上下文。resetSynthesizer.ts把currentProg恢复为模板经过desugarAutoLabel预处理的深拷贝并清空currentMutations。这样保证一次generateSubstance()只对应一个独立的合成过程多个程序之间互不污染。三、SynthesizerSetting一份完整的配置说明书初始化合成器必须提供SynthesizerSetting。该接口Synthesizer.ts由以下几个维度组成type All *; type ArgOption existing | generated | mixed; type ArgReuse distinct | repeated; export type MatchSetting string[] | All; // * 表示不限制 export type DeclTypes { [t in SubStmtKind]: MatchSetting }; // SubStmtKind type | predicate | constructor | function export interface SynthesizerSetting { mutationCount: [number, number]; // 每个程序执行的变异次数区间 [min, max] argOption: ArgOption; // 生成语句参数时复用已有 ID / 新生成 ID / 混合 argReuse: ArgReuse; // 复用 ID 时同一 ID 是否可重复出现在多个参数位 weights: { type: number; predicate: number; constructor: number }; opWeights: { [t in MutationType]: number }; // add / delete / edit 三种操作的权重 add: DeclTypes; // 允许新增的语句种类按 kind 过滤值为名称列表或 * delete: DeclTypes; // 允许删除的语句种类 edit: DeclTypes; // 允许编辑的语句种类 }3.1 各配置项语义与底层影响mutationCountgenerateSubstance()用this.random(...this.setting.mutationCount)从区间内随机取一个整数作为本程序的变异步数Synthesizer.ts。区间两端都闭。argOption与argReuse在generateArgSynthesizer.ts中直接决定参数如何产生existing从declaredIDs中按参数类型含subTypesOf子类型查找已声明的 IDdistinct模式下会排除usedIDs中已用过的 IDrepeated则允许重复使用。若找不到可用 ID会回退到generated模式自动生成一个新 ID。generated根据domEnv.typeDecls生成一个新的Decl语句并返回其名字。mixed对每个参数随机选择existing或generated。weights控制新增语句时在「类型 / 谓词 / 构造子」之间如何选择。注意该结构中并没有单独的 function 权重——函数声明在enumerateAdd中同样被枚举但带权选择仅覆盖上述三类。opWeightsmutateProgram中通过weightedChoice在add/delete/edit三种操作类型里做一次带权抽样Synthesizer.ts再进入对应的枚举分支。add/delete/editDeclTypes按SubStmtKind给出每类语句的匹配设置。值为*表示不限制值为字符串数组时只允许数组内列出的名称。这些配置经filterContextSynthesizer.ts作用过滤domEnv中的types、typeDecls、functionDecls、predicateDecls、constructorDecls从而缩小每一步变异时可选的候选集。delete与edit还会以模板程序template为参照做二次过滤避免破坏模板中定义的关键结构。3.2 仓库中的真实配置示例Edgeworth 的预设配置 给出了可直接复制的完整样例Lewis 分子结构预设const lewisParams: SynthesizerSetting { mutationCount: [1, 4], argOption: existing, argReuse: distinct, weights: { type: 0.15, predicate: 0.5, constructor: 0.35 }, opWeights: { add: 0, delete: 0, edit: 1 }, // 只做编辑不做增删 add: { type: *, function: *, constructor: *, predicate: * }, delete: { type: *, function: *, constructor: *, predicate: * }, edit: { type: *, function: *, constructor: *, predicate: * }, };而 Synthesizer.test.ts 中的defaultSetting展示了另一种风格opWeights均分0.3333且add/delete/edit各字段初始为空数组表示默认不开启。两者对照可以看出权重决定改不改DeclTypes 决定改什么二者共同塑造变体的多样性方向。四、生成流水线从配置到一批 Substance 程序4.1 顶层入口generateSubstances(numProgs)批量生成是 UI 层最常用的入口Synthesizer.ts用_.times(numProgs, oneSubstance)逐个生成每个程序生成完毕后立刻reset()用dedupSynthesizedSubstances按prettySubstance字符串比较去重若去重后数量不足numProgs继续循环补足直到凑齐指定数量的互不相同程序。每个SynthesizedSubstance返回三件套Synthesizer.tsexport interface SynthesizedSubstance { prog: SubProgA; // 合成后的 Substance AST ops: Mutation[]; // 本程序经历的变异操作记录 src: string; // pretty-printed 的 Substance 源码文本 }ops与src让上层 UI 既能展示程序长什么样也能展示经历了哪些操作。4.2 单程序合成generateSubstance()Synthesizer.ts核心流程generateSubstance (): SynthesizedSubstance { const numStmts this.random(...this.setting.mutationCount); // 随机变异步数 _.range(numStmts).reduce((ctx, n) { const newCtx this.mutateProgram(ctx); // 每步做一次变异 return newCtx; }, initContext(this.domEnv, this.subEnv, ...)); const prog sortStmts(dedupStmts(this.currentProg)); // 排序 去重防编译错误 return { prog, ops: this.currentMutations, src: prettySubstance(prog) }; };其中sortStmts与dedupStmts都来自SubstanceAnalysis.ts前者按「Decl → ApplyPredicate → Bind → AutoLabel → LabelDecl → NoLabel」的类别顺序加源码字典序重排SubstanceAnalysis.ts后者按 pretty 字符串去重——两者共同确保随机变异不会产出重复或顺序敏感的编译错误。4.3 单步变异mutateProgram(ctx)每步变异遵循「先枚举、后带权抽样、再执行」三段式Synthesizer.ts枚举三组候选enumerateAdd(addCtx)、enumerateDelete(deleteCtx)、enumerateUpdate(editCtx)各上下文分别由filterContext按add/delete/edit配置过滤得到带权选操作类型weightedChoice依据opWeights抽出add | delete | edit执行executeMutations(mutationGroup, this.currentProg, ctx)依次应用组内所有变异把结果写回currentProg并追加到currentMutations无候选时递归重试若当前类型没有可用变异则递归调用自身重新抽样保证每步都能产生变化。三个枚举器的工作方式enumerateAddSynthesizer.ts从nonEmptyDecls(ctx)当前上下文中仍有候选声明的语句种类里随机选一种然后生成对应的新语句组TypeDecl→ 生成一个DeclgenerateDeclPredicateDecl/FunctionDecl/ConstructorDecl→ 调用对应的generatePredicate/generateFunction/generateConstructor返回「新参数声明 新语句」的组合打包成一组Add变异。enumerateDeleteSynthesizer.ts随机选一个语句种类和名称findStmt定位语句对Bind/Decl这类返回值语句调用cascadingDelete找出所有引用其返回值的语句一并删除其余语句直接单删。enumerateUpdateSynthesizer.ts随机挑一条语句调用findMutations枚举该语句上所有可用的编辑变异。五、变异类型全表Add / Delete / UpdateMutation.ts定义了完整的变异类型系统Mutation.tsexport type MutationGroup Mutation[]; export type Mutation Add | Delete | Update; export type MutationType add | delete | edit; export interface MutationBase { tag: Mutation[tag]; additionalMutations?: Mutation[]; // 伴随变异如改类型时的连带增删 mutate: (op, prog, ctx) WithContextSubProgA; // 实际执行函数 } export type Update | SwapExprArgs | SwapStmtArgs | SwapInStmtArgs | SwapInExprArgs | ReplaceStmtName | ReplaceExprName | ChangeStmtType | ChangeExprType;每种Update变异的具体语义可由showMutation的输出印证Mutation.ts变异标签语义适用语句SwapStmtArgs交换谓词语句两个参数的位置ApplyPredicateSwapExprArgs交换 Bind 表达式中两个参数的位置Bind的ApplyConstructor/ApplyFunction/FuncSwapInStmtArgs把谓词语句的某个参数替换为同类型的另一个 IDApplyPredicateSwapInExprArgs把 Bind 表达式中的某个参数替换为同类型 IDBindReplaceStmtName将谓词语句名替换为签名相同的另一声明ApplyPredicateReplaceExprName将 Bind 中的表达式名替换为签名相同的另一声明BindChangeStmtType整体替换谓词语句为签名兼容的新语句连带删除旧语句、补上新声明ApplyPredicateChangeExprType替换 Bind 表达式若新语句返回值类型变化则cascadingDelete旧绑定Bind5.1 守卫函数check*与枚举器enum*守卫函数checkAddStmt(s)、checkSwapStmtArgs、checkReplaceStmtName、checkSwapInStmtArgs、checkChangeStmtType等Mutation.ts负责判断某条语句在当前上下文下能否执行某类变异能则构造对应的Mutation对象并返回不能则返回undefined。守卫函数会把候选选择以回调形式交给Synthesizer由choice完成随机挑选例如交换参数时只在类型相同的索引对中选择用getSignature比较args[i] args[j]。枚举器enumSwapStmtArgs、enumReplaceStmtName、enumChangeExprType等Mutation.ts则是穷举某条语句的全部可行变异。mutationEnumerators数组把它们统一注册enumerateStmtMutations与enumerateProgMutations分别提供单语句枚举和全程序枚举。5.2 执行函数与上下文同步executeMutation/executeMutationsMutation.ts逐个调用MutationBase.mutate。关键在于增删Decl时要同步维护上下文appendStmtCtx若新增的是Decl先addID(ctx, stmt.type.name.value, stmt.name)再追加语句removeStmtCtx若删除的是Decl先removeID再移除语句Mutation.ts。这正是 README 所述上下文负责实际操作与记账在代码层面的直接体现。六、纯函数分析层SubstanceAnalysisREADME 指出Synthesizer与SynthesisContext在与 Substance AST 交互时统一使用analysis/SubstanceAnalysis中的纯函数来创建或转换 AST 节点。这一约定保证了所有 AST 操作可预测、无副作用、便于测试。核心纯函数包括SubstanceAnalysis.ts结构操作appendStmt、removeStmt、replaceStmt、getStmt、subProg——均为返回新 AST的不可变风格实现节点工厂applyConstructor/applyFunction/applyPredicate/applyBind/applyTypeDecl/nullaryTypeCons/labelStmt——把 Domain 声明转换成 Substance AST 节点统一打上nodeType: SyntheticSubstance标记与用户手写节点区分签名匹配getSignature、signatureEquals、signatureArgsEqual、matchDecls、matchSignatures、argMatches、findDecl、identicalTypeDecls——支撑ReplaceName、SwapIn、ChangeType等变异候选的合法性判定删除分析cascadingDelete——给定一个将被删除的Bind/Decl用栈式搜索迭代找出所有引用其返回值/变量的语句并一起返回SubstanceAnalysis.ts避免生成悬空引用清洗与比较cleanNode剔除metaProps元属性、nodesEqual、progsEqual、intersection、sortStmts、dedupStmts、dedupSynthesizedSubstances类型发现findTypes/mergeKindMaps/typeOf——从程序中提取用到的 type/predicate/function/constructor 名称集合用于filterContext的二次过滤。其中cascadingDelete是保证删除安全的关键删除Set A时若存在Set C : Subset(B, A)、Set E : Intersection(D, C)这类依赖链它会递归地把C、E及其绑定语句全部纳入删除集。这一行为被 Synthesizer.test.ts 中的 cascading delete 用例直接验证删除Set A后程序被正确化简为Set B/Set D。七、进阶能力Search.ts的差分与路径搜索Search.tsSearch.ts提供了 README 未展开但同属合成模块核心的能力在任意两个 Substance 程序之间计算差异并搜索如何从一个变异到另一个的路径。这用于把用户编辑后的程序反向映射回变异操作序列。7.1 AST 差分DiffdiffSubProgs用recursive-diff的getDiff计算两棵 AST 的精确差异并过滤掉路径中涉及metaProps位置、nodeType 等元信息的噪音差异diffSubStmts先对两边程序sortStmts归一化顺序再做精确差分输出带语句索引的StmtDiff[]subProgDiffs语句级差分产出DiffSet { add, delete, update }三分类结果——先用intersection找公共语句再对剩余语句用similarNodes节点相等或拥有共同后代建立相似映射最终把相似对判为update、把多出/缺失判为add/delete。Search.test.ts 展示了典型结果两个集合程序之间的差异会被正确识别为Delete: Equal(E, E)与两条Update含具体到(variable,value) - E这样的细粒度路径描述。7.2 变异路径搜索findMutationPaths(src, dest, ...)对DiffSet中的每条 update在源语句上用enumerateStmtMutations枚举候选变异用executeMutationprettySubstance字符串比对筛选出能精确还原目标语句的变异再与 add/delete 变异做笛卡尔积组合成完整路径组enumerateAllPaths不做匹配筛选直接对所有候选变异做笛卡尔积产出全部可行路径enumerateMutationPaths(src, dest, initCxt, maxDepth)基于 BFS 的枚举式搜索从src出发逐层生长候选程序每层先enumerateProgMutations枚举、再executeMutation执行并用prettySubstance字符串去重观测等价observational equivalence剪枝直到深度maxDepth或命中目标。测试用例Search.test.ts验证了它能找到包含SwapStmtArgsChangeExprTypeReplaceStmtName的最短三步路径。八、上层调用方Edgeworth 如何驱动合成器synthesis模块不是孤立的——Edgeworth 应用通过两条路径消费它批量变体生成problems/util.tsxgenerateProgs中先用compileDomain编译 Domain可选地用compileSubstance编译模板 Substance然后new Synthesizer(domEnv, subEnv, setting, subRes, seed)并调用synth.generateSubstances(numPrograms)。模板程序getTemplate()会被放在结果列表首位作为变体的原图对照。设置面板components/Settings.tsxUI 提供Pick a Domain下拉与Input Scenario编辑框点击 Generate Variations 时把当前SynthesizerSetting、seed、程序数量、domain/style/substance 一起交给回调More Variations 按钮会通过generateVariation()颜色动物名随机数字拼出的随机 seed生成全新种子后再触发一次生成。九、如何运行与验证synthesis模块的测试基于 Vitestvitest-environment jsdom可在仓库根目录按序执行yarn install yarn vitest run packages/edgeworth/src/synthesis现有测试覆盖三类能力Synthesizer.test.ts级联删除的正确性Mutation.test.ts单语句枚举例如对三元谓词Equal3(A, B, C)恰好枚举出 3 条SwapStmtArgsSearch.test.tsAST 差分、变异识别swap/change type/replace name、含噪音的多变异路径搜索。若要在浏览器中体验合成器 UI可进入packages/edgeworth后执行yarn start开发模式或yarn build产物输出到dist在设置面板中选择 Domain、调整配置并点击 Generate Variations 批量产出变体图。十、小结synthesis模块通过「Synthesizer决策 SynthesisContext记账 SubstanceAnalysis纯函数执行」的三层协作把生成一个合法的 Substance 程序变体拆解为可配置、可复现、可审计的变异流水线SynthesizerSetting控制变异预算与候选范围Mutation类型系统统一描述增删改操作Search.ts则提供反向的差分与路径搜索能力。无论是为penrose/synthesizer提供批量训练数据还是像 Edgeworth 那样为教学场景生成选择题选项这套架构都构成了 Penrose 自动生成内容diagram synthesis的基础设施。赞分享开发工具数据可视化【免费下载链接】penroseCreate beautiful diagrams just by typing notation in plain text.项目地址https://gitcode.com/gh_mirrors/pe/penrose点击查看免费下载相关推荐escrcpy安卓投屏完整指南零门槛把手机屏幕镜像到电脑escrcpy安卓投屏完整指南零门槛把手机屏幕镜像到电脑 用 escrcpy 做安卓投屏把手机画面实时镜像到电脑鼠标键盘都能直接操控手机。它完全免费、无需开发工具数据可视化Tikv内存引擎在线配置变更失效问题分析Tikv内存引擎在线配置变更失效问题分析 在Tikv项目的内存引擎 Range Cache Memory Engine 实现中发现了一个关于配置动态更新的设计数据库KV存储分布式数据库云原生深入解析 k6 内置 JavaScript 引擎 sobek 的 parser 包从源码到 AST 的完整指南深入解析 k6 内置 JavaScript 引擎 sobek 的 parser 包从源码到 AST 的完整指南 k6 是一个使用 Go 编写、以 JavaSc测试开发工具CI/CD上一篇如何快速搭建FaceSwap环境Windows与Linux完整部署指南下一篇CodeGuide Netty 源码分析从 NioEventLoopGroup 拆解 Netty 事件循环组的继承体系与线程模型创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表