多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

syntax词法器Start Conditions完全指南:3步掌握有状态Tokenization与注释行号统计

syntax词法器Start Conditions完全指南:3步掌握有状态Tokenization与注释行号统计 syntax词法器Start Conditions完全指南3步掌握有状态Tokenization与注释行号统计【免费下载链接】syntaxSyntactic analysis toolkit, language-agnostic parser generator.项目地址: https://gitcode.com/gh_mirrors/sy/syntaxsyntax 是一款语言无关的解析器生成器LR/LL 解析算法其内置词法器tokenizer支持Start Conditions起始条件——也就是所谓的「有状态 Tokenization」。这意味着同一条 lex 规则可以在不同状态下产生不同的 token让词法器像有限状态机一样工作。本文将带你在 3 步内理解并实战这一特性包括一个经典应用在跳过注释的同时精确统计行号。为什么需要词法器状态先想一个真实场景词法器遇到/*后要一直忽略到*/之间的所有内容但如果注释里包含多行这些换行也必须被计入行号否则报错位置会全部错位。如果词法器是无状态的你很难表达「仅在注释内部才生效的规则」。而 Start Conditions 正是为此设计每条 lex 规则可以声明一个状态名只有词法器处于该状态时规则才会被执行词法器初始处于INITIAL状态通过pushState(name)/popState()进出状态状态形成一个栈可以嵌套进入多个状态popState()弹出后自动回到上一个状态。// 状态切换三件套JavaScript 词法动作中 this.pushState(comment); // 进入 comment 状态begin 是别名 this.popState(); // 退出 comment 状态核心概念独占状态与包容状态Start Conditions 分为两种类型在 JSON 格式的语法中通过startConditions字段声明类型符号值进入该状态后的行为包容型inclusive%s0状态内规则 无状态条件的普通规则都会执行独占型exclusive%x1仅该状态的规则执行屏蔽其他普通规则通配*—标记为*的规则在任何状态下都执行一个典型的comment状态声明独占型确保注释内部只跑注释规则lex: { startConditions: { comment: 1, // exclusive }, rules: [ [\\/\\*, this.pushState(comment);], // 遇 /* 进入注释状态 [[comment], \\*\\/, this.popState();], // 仅 comment 状态遇 */ 退出 [[comment], \\n, lines;], // 仅 comment 状态统计注释内换行 [\\n, lines], // INITIAL 状态统计普通换行 ], } 规则数组的第一个元素是状态名列表例如[comment, string]表示该规则在两个状态中均生效。实战注释行号统计完整示例项目自带了一个非常直观的示例examples/lexer-start-conditions.g.js它解析一段只含Main关键字的「程序」同时正确统计包含注释内部的总行数。对以下输入/* Hello world privet OK **/ Main注释整体被跳过不产生任何 token但注释里的 3 个换行依然被[comment, \\n]规则捕获并累加lines普通代码中的换行由 INITIAL 状态的\\n规则累加。最终输出行数正确无误。该示例在 Pythonexamples/lexer-start-conditions.py.g和 Rubyexamples/lexer-start-conditions.rb.g下各有一份逻辑完全一致只是状态 API 略有不同Pythonself.push_state(comment)/self.pop_state()Rubypush_state(comment)/pop_state()JavaScriptthis.pushState(comment)/this.popState()生成并运行以 JavaScript 为例./bin/syntax -g examples/lexer-start-conditions.g.js -m slr1 -f ~/test.js不只是注释Start Conditions 的隐藏大招项目自身的BNF 解析器src/generated/bnf.g就用action独占状态解决了一个更棘手的问题语法文件中的 JS 处理函数action本身被{}包裹而函数体内部还可能出现{}、引号、正则字面量词法器在进入第一个{时执行this.pushState(action)随后所有字符都按「代码」逐段收集字符串、注释、正则各有专门规则并配合yy.depth计数花括号嵌套只有当嵌套深度归零时才popState()回到正常状态。这正是有状态 Tokenization 的价值同一字符如{在不同状态下产生完全不同的 token。核心源码在哪里看如果你想深入实现细节这几个文件值得一读src/tokenizer.js—— 内置词法器pushState/popState/getCurrentState都在这里状态栈初始为[INITIAL]src/grammar/lex-rule.js—— lex 规则的数据结构解析规则携带的状态名src/grammar/lex-grammar.js—— 词法语法整体包括startConditions的存储src/plugins/—— 各语言插件Python、PHP、Ruby、C、C#、Rust、Java、Julia同一套 Start Conditions 语法可生成对应语言的解析器官方文档入口见项目根目录README.md中的 “Start conditions of lex rules, and tokenizer states” 章节。新手常见问题 FAQQ1包容状态inclusive和独占状态exclusive怎么选绝大多数场景注释、字符串字面量应使用独占状态值为1你希望进入注释后普通规则全部失效只按注释规则消费字符。包容状态适合「在普通规则之上叠加额外规则」的场景。Q2*通配条件有什么用把某些规则如跳过空白标记为*状态后它们在任何状态下都会执行非常适合全局规则。Q3状态可以嵌套吗可以。状态是栈结构pushState压栈、popState弹栈因此可以表达「注释内的字符串字符串」这类层层嵌套的词法上下文。Q4语法层面BNF也能感知状态吗词法动作中可以通过yy.lexer别名yy.tokenizer直接拿到词法器实例实现「解析器 ↔ 词法器」双向通信例如在表达式位置切换状态来区分{x: 1}是语句块还是对象字面量见examples/parser-lexer-communication.g。小结特性说明Start Conditions让 lex 规则绑定状态实现有状态词法分析独占 / 包容状态1exclusive屏蔽普通规则0inclusive叠加普通规则状态栈pushState/popState支持嵌套上下文典型应用注释跳过 行号统计、字符串字面量、解析器-词法器协作掌握了 Start Conditions你的 syntax 词法器就具备了处理真实编程语言词法层的能力——这也是从「玩具解析器」迈向「实用编译器前端」的关键一步。【免费下载链接】syntaxSyntactic analysis toolkit, language-agnostic parser generator.项目地址: https://gitcode.com/gh_mirrors/sy/syntax创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表