开发工具编译器【免费下载链接】ohmA library and language for building parsers, interpreters, compilers, etc.项目地址https://gitcode.com/gh_mirrors/oh/ohm点击查看免费下载本篇指南讲解如何将解析库 Ohmohm-js与 TypeScript 结合使用既有开箱即用的核心 API 类型定义也有自 Ohm v16 起提供的按文法生成类型定义grammar-specific type definitions能力——让 TypeScript 编译器校验你的语义动作semantic actions的参数个数与返回类型并让 VS Code 等 IDE 获得动作名自动补全与节点类型提示。读完本文你将掌握ohm-js/cli的generateBundles工作流、生成物.ohm-bundle.js与.ohm-bundle.d.ts的内部结构以及如何用仓库内arithmetic示例搭建一个可类型检查的 TypeScript 解析器项目。开箱即用内置类型定义覆盖核心 APIohm-js包本身自带完整的 TypeScript 类型声明。在 packages/ohm-js/package.json 中可以看到types: index.d.ts指明类型入口exports字段中同时为 ESMimport与 CommonJSrequire两种加载方式声明了types: ./index.d.ts。因此只需正常安装依赖TypeScript 编译器与 IDE 即可识别 Ohm 的核心 API。这份手写类型声明位于 packages/ohm-js/index.d.ts覆盖了顶层函数与常量grammar(source, namespace?)、grammars(source, namespace?)、ohmGrammar、ExperimentalIndentationSensitive、pexprs解析表达式构造函数集、versionGrammar接口match()、matcher()、trace()、createSemantics()、extendSemantics()、isBuiltIn()以及rules、superGrammar等属性Matcher接口支持增量编辑输入流的setInput()、replaceInputRange()、match()、trace()MatchResult接口succeeded()、failed()、message、shortMessage、getInterval()Semantics接口addOperationT()、addAttributeT()、extendOperationT()、extendAttributeT()语义动作相关类型ActionT、ActionDictT、BaseActionDictT覆盖_iter、_nonterminal、_terminal特殊动作以及letter、digit、ListOf等内置规则节点类型体系Node及三个子接口TerminalNode、NonterminalNode、IterationNode以及Interval、LineAndColumnInfo、RuleInfo等工具类型。需要说明的是内置类型声明覆盖了核心 API但仍有少量低频使用的部分尚未纳入例如文件头注释明确写到部分 API not (yet) there。如果发现缺失可以到项目仓库提交 issue 或 PR 补充。文法专属类型定义让编译器检查你的语义动作默认的手写类型声明只能给出泛化的语义动作签名——每个动作都接收Node[]返回类型不受约束。自 Ohm v16 起新增了针对你具体文法生成类型定义的能力其核心收益有两个TypeScript 编译器可以校验语义动作的参数个数是否与文法规则匹配并保证同一操作operation下所有动作返回类型一致IDE 可以获得文法级智能提示自动补全动作名如AddExp_plus、PriExp_paren并在悬停时展示参数的具体类型——IterationNode、NonterminalNode或TerminalNode。三步启用流程按 doc/typescript.md 的说明启用文法专属类型只需三步第一步安装 CLI 工具npm install -D ohm-js/cli将其作为开发依赖devDependency安装。ohm-js/cli的版本信息见 packages/cli/package.json它提供ohm命令行入口bin: { ohm: index.js }。第二步把文法放入独立的.ohm文件如果文法目前内嵌在 JS 字符串里例如String.raw模板串请将它抽到独立的.ohm文件中例如src/my-grammar.ohm。第三步用 CLI 生成 bundle 与类型定义npx ohm generateBundles --withTypes src该命令会扫描src目录下的所有.ohm文件并为每个文件生成两个产物src/my-grammar.ohm-bundle.js—— 可直接导入的独立模块bundlesrc/my-grammar.ohm-bundle.d.ts—— 配套的 TypeScript 类型定义。随后即可直接导入 bundle 使用import grammar from ./my-grammar.ohm-bundle;至此grammar.createSemantics()返回的将不再是泛化的Semantics而是按你的文法特化的语义对象——编译器与 IDE 都能看懂你的动作字典。CLI 命令与参数详解generateBundles命令定义于 packages/cli/src/commands/generateBundles/index.js其完整声明为参数/选项说明generateBundles patterns...要处理的.ohm文件 glob 模式可传多个内部用fast-glob展开-t, --withTypes同时生成对应的.d.ts文件TypeScript 专用-e, --esm以 ES module 格式生成 bundle默认是 CommonJS-n, --dryRun全局选项见 packages/cli/src/cli.js只打印将要生成的文件路径而不实际写入几个值得注意的实现细节只有扩展名为.ohm的文件会被处理源码中OHM_FILE_EXT常量其他文件会被跳过输出文件名是输入路径 -bundle后缀输入src/my-grammar.ohm会得到src/my-grammar.ohm-bundle.js与src/my-grammar.ohm-bundle.d.tsCommonJS 模式下 bundle 头部是use strict;const {makeRecipe}require(ohm-js);并以module.exportsresult;导出--esm模式下则为import {makeRecipe} from ohm-js;与export default result;每个生成文件都会附带一个// AUTOGENERATED FILE横幅标明由哪个源文件经ohm generateBundles生成。除了generateBundlesCLI 还提供match子命令见 packages/cli/src/commands/match.js可快速用文法匹配一个输入文件、在命令行验证文法行为无需编写任何 JS 代码npx ohm match -f src/my-grammar.ohm input.txt # -g name 指定使用文件中的哪个文法默认取最后一个 # -f 也支持传入 .ohm-bundle.js生成物解剖bundle 与 .d.ts 的内部结构以仓库中 packages/packaging-tests/src/greeting-esm.ohm-bundle.js 为例由Greeting { hello Hello! }生成ESM 格式的 bundle 本质是一个序列化的文法配方recipeimport {makeRecipe} from ohm-js; const result makeRecipe([grammar, {source:Greeting {\n hello \Hello!\\n}}, Greeting, null, hello, {hello:[define,{sourceInterval:[13,29]},null,[], [terminal,{sourceInterval:[21,29]},Hello!]]}]); export default result;也就是说bundle 并不重新解析文法源码而是把文法编译后的内部表示recipe打包成模块导入时通过makeRecipe还原出 Grammar 实例——这正是一次编译、随处导入的实现基础。对应的类型文件 packages/packaging-tests/src/greeting-esm.ohm-bundle.d.ts 结构如下// AUTOGENERATED FILE // This file was generated from greeting-esm.ohm by ohm generateBundles. import { BaseActionDict, Grammar, IterationNode, Node, NonterminalNode, Semantics, TerminalNode } from ohm-js; export interface GreetingActionDictT extends BaseActionDictT { hello?: (this: NonterminalNode, arg0: TerminalNode) T; } export interface GreetingSemantics extends Semantics { addOperationT(name: string, actionDict: GreetingActionDictT): this; extendOperationT(name: string, actionDict: GreetingActionDictT): this; addAttributeT(name: string, actionDict: GreetingActionDictT): this; extendAttributeT(name: string, actionDict: GreetingActionDictT): this; } export interface GreetingGrammar extends Grammar { createSemantics(): GreetingSemantics; extendSemantics(superSemantics: GreetingSemantics): GreetingSemantics; } declare const grammar: GreetingGrammar; export default grammar;可以观察到三类按文法特化的类型XxxActionDictT为文法中每条规则声明一个可选的动作签名。例如hello规则的右侧是终端Hello!因此动作签名是(this: NonterminalNode, arg0: TerminalNode) T——参数类型精确到了TerminalNodeXxxSemantics重写addOperation/addAttribute/extendOperation/extendAttribute使它们只接受该文法专属的XxxActionDictTXxxGrammar重写createSemantics()/extendSemantics()的返回类型让grammar.createSemantics()直接返回XxxSemantics。这些接口的生成逻辑位于 packages/cli/src/helpers/generateTypes.js 的createDeclarations()文法Arithmetic会得到ArithmeticActionDict、ArithmeticSemantics、ArithmeticGrammar三个接口外加declare const grammar默认导出。从源码看节点类型是如何计算出来的生成类型定义的关键环节是为每条规则的每个参数推导节点类型。这个推导器位于 packages/cli/src/helpers/getNodeTypes.js其规则可以概括为文法表达式pexpr推导出的参数类型any、end、Terminal、Range、UnicodeChar、CaseInsensitiveTerminalTerminalNodeApply引用其他规则NonterminalNodeParam规则形式参数NodeAlt\|选择各分支类型做并集union合并Seq序列各因子类型依次拼接flatMapIter*、、?每个被迭代的子表达式都对应一个IterationNodeNot~否定式无参数Lookahead、Lex递归求解内部表达式并集的收窄逻辑UnionType也值得留意如果并集里混入了Node或并集成员数量达到 3 个及以上则直接退化为宽泛的Node否则按类型名排序后以|连接。这保证了生成的签名既精确又不会过度膨胀。以文法G { start letters; letters letter }为例来自 packages/cli/src/helpers/generateTypes.test.ts 的测试快照生成的GActionDict为export interface GActionDictT extends BaseActionDictT { start?: (this: NonterminalNode, arg0: NonterminalNode) T; letters?: (this: NonterminalNode, arg0: IterationNode) T; }start的规则体引用lettersApply所以参数是NonterminalNodeletters的规则体是letterIter所以参数是IterationNode。这种规则体结构 → 参数类型的映射正是 IDE 工具提示与编译期校验的基础。完整实战arithmetic 示例仓库在 examples/typescript 目录下提供了一个完整的 TypeScript 示例项目是理解整套工作流的最佳范本。文法文件 examples/typescript/src/arithmetic.ohm 定义了一个支持四则运算、幂运算、括号、一元正负号、标识符与数字的算术文法Arithmetic { Exp AddExp AddExp AddExp MulExp -- plus | AddExp - MulExp -- minus | MulExp MulExp MulExp * ExpExp -- times | MulExp / ExpExp -- divide | ExpExp ExpExp PriExp ^ ExpExp -- power | PriExp PriExp ( Exp ) -- paren | PriExp -- pos | - PriExp -- neg | ident | number ident (an identifier) letter alnum* number (a number) digit* . digit -- fract | digit -- whole }该项目的 package.json 把生成命令固化成了 npm scriptscripts: { generate: ohm generateBundles --withTypes src/*.ohm, test: pnpm generate node -r ts-node/register src/arithmetic.test.ts }执行pnpm generate或npx ohm generateBundles --withTypes src/*.ohm即可生成src/arithmetic.ohm-bundle.js与src/arithmetic.ohm-bundle.d.ts。注意此处 glob 模式带了引号避免 shell 自行展开。核心代码 examples/typescript/src/arithmetic.ts 展示了完整用法import grammar, {ArithmeticSemantics} from ./arithmetic.ohm-bundle; const constants: {[name: string]: number} { pi: Math.PI, e: Math.E }; // 显式标注类型仅为演示这里的 createSemantics() 返回的不是泛化 // 的 Semantics而是按文法特化的 ArithmeticSemantics const semantics: ArithmeticSemantics grammar.createSemantics(); semantics.addOperationnumber(eval(), { AddExp_plus(x, _, y) { return x.eval() y.eval(); }, AddExp_minus(x, _, y) { return x.eval() - y.eval(); }, MulExp_times(x, _, y) { return x.eval() * y.eval(); }, MulExp_divide(x, _, y) { return x.eval() / y.eval(); }, ExpExp_power(x, _, y) { return Math.pow(x.eval(), y.eval()); }, PriExp_paren(_l, e, _r) { return e.eval(); }, PriExp_pos(_, e) { return e.eval(); }, PriExp_neg(_, e) { return -e.eval(); }, ident(_l, _ns) { return constants[this.sourceString] || 0; }, number(_) { return parseFloat(this.sourceString); } }); export function evaluate(expr: string): number { const matchResult grammar.match(expr); return semantics(matchResult).eval(); }要点解析addOperationnumber(eval(), ...)显式声明该操作返回number同时这也是 Ohm v16 起必须提供的类型参数详见下文版本注意事项动作名如AddExp_plus来自文法规则与备选分支后缀-- plus的组合。每个动作的形参个数、顺序与规则体结构一一对应——例如AddExp_plus(x, _, y)对应AddExp MulExp的左操作数、终端、右操作数三个子节点借助生成类型如果误写动作签名参数个数不对或返回类型不一致编译会直接报错。文件注释里就演示了这种情况若把number(_)误写成number(a, b)编译器会给出类似error TS2322: Type (a: any, b: any) number is not assignable to type (arg0: NonterminalNode) number的错误evaluate()对外暴露纯函数接口grammar.match(expr)得到MatchResult交给semantics调用eval()即可得到数值结果。配套的单元测试 examples/typescript/src/arithmetic.test.ts 验证了功能正确性import {test} from uvu; import * as assert from uvu/assert; import {evaluate} from ./arithmetic; test(basic, () { assert.is(evaluate((3 4) * 5), 35); assert.is(evaluate(pi), Math.PI); }); test.run();多文法文件与命名空间一个.ohm源文件里可以定义多个文法例如G及其扩展G2 : G。类型生成器对此有两种处理动作字典继承扩展文法生成的G2ActionDictT会直接extends GActionDictT见generateTypes.js中actionDictSuperType的逻辑因此为G2补充动作时仍保留G全部规则的动作签名。测试快照中的实际输出为export interface G2ActionDictT extends GActionDictT { start?: (this: NonterminalNode, arg0: NonterminalNode) T; digits?: (this: NonterminalNode, arg0: IterationNode) T; }命名空间导出源文件含多个文法时.d.ts不再使用单一默认导出而是导出declare const ns对象每个文法对应一个XxxGrammar字段declare const ns: { G: GGrammar; G2: G2Grammar; }; export default ns;相应地bundle 的运行时导出也从单个文法变为以文法名为键的对象。从源码看这一分支由generateBundles中的isSingleGrammar判断仅当文件中只有一个文法时才直接export default该文法本身。类型检查是如何被验证的ohm-js/cli自带一组基于avats-morph的测试位于 packages/cli/src/helpers/generateTypes.test.ts它们直接把生成出的.d.ts与用户代码交给 TypeScript 编译器做类型检查type check验证了以下场景场景期望结果单文法 动作签名正确无诊断错误多文法含G2 : G 各自正确动作无诊断错误动作参数个数错误start(letters, x)报错Type (letters: any, x: any) any is not assignable to type (this: NonterminalNode, arg0: NonterminalNode) number动作返回类型错误返回字符串而非number报错TS 错误码 2322extendSemanticsaddOperation/extendOperation正确用法无错误错误 arity 被检出这些测试证明文法专属类型不只是锦上添花的提示而是能在编译期拦截语义动作书写错误的硬性保障。版本注意事项由于文法专属类型与类型参数化机制与版本强相关使用时需注意以下几点Ohm v16 起addOperation、extendOperation、addAttribute、extendAttribute增加了必需的类型参数即操作返回类型Action与ActionDict也被参数化——同一操作下所有动作的返回类型必须一致。升级旧代码时把semantics.addOperation(toString, {...})改为semantics.addOperationstring(toString, {...})即可必要时可使用any详见 doc/releases/ohm-js-16.0.mdOhm v17 起ES module 形态下ohm-js与ohm-js/extras只提供命名导出不再有默认导出因此应写作import * as ohm from ohm-js;而不是import ohm from ohm-js;详见 doc/releases/ohm-js-17.0.md导入 bundle 与esModuleInteropCommonJS 格式的 bundle 以module.exports导出而.d.ts声明了默认导出在 TypeScript 中import grammar from ./my-grammar.ohm-bundle需要esModuleInterop或allowSyntheticDefaultImports支持——tsconfig.json里开启esModuleInterop: true即可参考 examples/typescript/tsconfig.json生成物是编译产物文法一旦修改需要重新运行generateBundles。建议像 arithmetic 示例那样把生成命令写入package.json的scripts在测试或构建前执行。小结与延伸阅读在 Ohm 中使用 TypeScript 分为两个层次默认的手写类型声明packages/ohm-js/index.d.ts保证核心 API 开箱即用而ohm-js/cli的generateBundles --withTypes工作流则把类型精度提升到文法级——每个语义动作的参数类型都由规则体结构推导而来TerminalNode/NonterminalNode/IterationNode动作名可自动补全参数个数与返回类型由编译器把关。这套机制在 examples/typescript 示例中有完整落地可作为新项目的最小模板。进一步阅读仓库内相关文档doc/typescript.md —— 本文所依据的官方 TypeScript 使用说明doc/api-reference.md ——Grammar、Semantics、Node等 API 的完整参考doc/patterns-and-pitfalls.md —— 迭代节点、可选节点的惯用处理方式含可选链操作符等现代写法doc/releases/ohm-js-16.0.md 与 doc/releases/ohm-js-17.0.md —— 涉及类型参数与导入方式的破坏性变更说明packages/cli/src/helpers/generateTypes.test.ts —— 类型检查行为的可执行测试用例。赞分享开发工具编译器【免费下载链接】ohmA library and language for building parsers, interpreters, compilers, etc.项目地址https://gitcode.com/gh_mirrors/oh/ohm点击查看免费下载相关推荐RE2类型定义_re2.d.ts与TypeScript类型支持RE2类型定义_re2.d.ts与TypeScript类型支持 RE2作为快速、安全的正则表达式引擎在TypeScript环境中的应用需要可靠的类型定义保障后端DeepSeek-Coder-V2面向企业级代码智能的开源解决方案DeepSeek Coder V2面向企业级代码智能的开源解决方案 在当今快速迭代的软件开发环境中开发团队面临着多语言技术栈集成困难、代码质量参差不齐、技术人工智能大模型代码模型DeepSeekCSS模块类型定义Lightning CSS生成.d.ts文件方案CSS模块类型定义Lightning CSS生成.d.ts文件方案 在前端开发中CSS模块CSS Modules为样式作用域提供了有效解决方案但类型定前端开发工具上一篇揭秘DWMBlurGlassWindows标题栏美化的底层革新与性能优化下一篇3分钟永久激活IDM开源脚本解锁无限下载体验创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考