如何在Nearley中结合自定义语法与JS语法(无需定义完整JS语法)
实现自定义语法与JS原生语法的混合解析(基于Nearley+Moo)
核心思路借鉴JSX的实现逻辑:通过词法分析器的状态切换区分自定义语法和JS代码,将JS内容作为黑盒字符串传递,无需在Nearley中定义完整JS语法,具体步骤如下:
1. 用Moo实现多状态词法分析
利用Moo的状态切换功能,定义两个核心状态:js(处理原生JS代码)和custom(处理自定义<tag>语法),在两种状态间自动切换:
const moo = require('moo'); const lexer = moo.states({ // JS状态:匹配所有非<的内容,遇到<则切换到自定义语法状态 js: { customTagOpen: { match: '<', push: 'custom' }, jsContent: { match: /[^<]+/, lineBreaks: true }, }, // 自定义语法状态:处理标签结构,遇到>切回JS状态 custom: { tagName: /[a-zA-Z_][a-zA-Z0-9_]*/, customTagClose: { match: '>', pop: 1 }, whitespace: /\s+/, }, });
2. 在Nearley中定义混合语法规则
无需编写JS语法规则,只需将JS内容作为整体块处理,与自定义标签规则并列:
# 顶层入口:允许多个JS块或自定义标签的混合 main -> topLevelElement* {% (d) => d[0] %} # 顶层元素:要么是JS代码块,要么是自定义标签 topLevelElement -> jsBlock | customTag # JS代码块:拼接连续的JS内容,保留原始字符串 jsBlock -> jsContent:+ {% (d) => ({ type: 'js', code: d[0].join('') }) %} # 自定义标签:解析<tag>结构,生成对应AST节点 customTag -> customTagOpen whitespace? tagName whitespace? customTagClose {% (d) => ({ type: 'customTag', name: d[2].value }) %}
3. 扩展:支持自定义标签内嵌入JS表达式
如果需要类似JSX的{表达式}嵌入逻辑,只需在Moo的状态规则中添加切换逻辑,遇到{切回JS状态,处理完}再返回:
// 更新后的custom状态 custom: { tagName: /[a-zA-Z_][a-zA-Z0-9_]*/, jsExprOpen: { match: '{', push: 'js' }, jsExprClose: { match: '}', pop: 1 }, customTagClose: { match: '>', pop: 1 }, whitespace: /\s+/, } // 同时更新JS状态,识别}并切回custom js: { customTagOpen: { match: '<', push: 'custom' }, jsExprClose: { match: '}', pop: 1 }, jsContent: { match: /[^<}]+/, lineBreaks: true }, }
对应的Nearley规则也可以扩展,允许自定义标签中插入JS表达式节点。
4. 后续AST处理
解析完成后,你会得到包含两种类型节点的混合AST:
- 对于
js类型的节点,直接将其code字段交给专业JS解析工具(如@babel/parser)生成标准JS AST; - 对于自定义标签节点,按照你的业务逻辑处理;
- 最后将两种AST按顺序合并或转译为目标代码即可。
这种方式既复用了成熟的JS解析工具,又能快速集成自定义语法,完全符合需求。
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

