
本文详解如何通过 peg.js 的语义动作(semantic actions)自定义解析结果,将算术表达式计算值格式化为十进制、十六进制和二进制三行字符串输出,并支持多行输入与独立结果分隔。
本文详解如何通过 peg.js 的语义动作(semantic actions)自定义解析结果,将算术表达式计算值格式化为十进制、十六进制和二进制三行字符串输出,并支持多行输入与独立结果分隔。
PEG.js 默认将匹配结果作为 JavaScript 值返回,而其核心优势之一在于:你可在每条语法规则末尾添加 { … } 语义动作,完全控制该规则的返回值。要实现“一行输入 → 三行输出(Decimal/Hex/Bin)”,关键在于:在最终求值规则中执行计算,并用换行符 n 拼接三种进制格式字符串。
以下是一个完整、可运行的示例语法(兼容 PEG.js v0.10+):
// arithmetic.pegjs Start = _ expr:Expression _ { return expr; } Expression = left:Term op:("+" / "-") right:Expression { const l = number(left); const r = Number(right); return op === "+" ? l + r : l - r; } / Term Term = left:Factor op:("*" / "/") right:Term { const l = Number(left); const r = Number(right); return op === "*" ? l * r : (r !== 0 ? l / r : NaN); } / Factor Factor = "(" _ expr:Expression _ ")" { return expr; } / number:Integer { return number; } Integer = digits:[0-9]+ { const n = parseInt(digits.join(''), 10); // 生成三行:十进制(原样)、十六进制(小写前缀 0x,补零至2位)、二进制(补零至8位) const hex = '0x' + n.toString(16).padStart(2, '0'); const bin = n.toString(2).padStart(8, '0'); return `${n}n${hex}n${bin}`; } _ "whitespace" = [ tnr]* // 支持多行输入:每个非空行独立解析并输出,用空行分隔 Lines = line:(!("n" / "") .)* "n"? { return line.join(''); }
✅ 使用说明:
- 将上述语法粘贴至 PEG.js Online Editor 或本地构建工具;
- 输入 (3*(5+8)) → 输出:
39 0x27 00100111- 输入多行(如 42n(8*7)),需配合外部 JS 代码按行分割并逐行调用 parser.parse(),PEG.js 本身不自动处理多行——这是设计使然(专注单表达式解析),但极易扩展。
⚠️ 重要注意事项:
- text() 在 Integer 规则中不可直接使用(它返回原始匹配字符串,未去除空白),应改用捕获组(如 digits:[0-9]+)再拼接;
- 进制转换务必使用 Number(…).toString(radix) 而非 parseInt(…, radix),后者用于解析而非格式化;
- 若需统一输出宽度(如 00100111),请用 .padStart(width, ‘0’);十六进制建议加 0x 前缀提升可读性;
- 多行输入需在宿主 JavaScript 中实现:input.split(‘n’).filter(line => line.trim()).map(line => parser.parse(line.trim()))。
总结而言,PEG.js 的语义动作是输出定制的核心机制。无需修改生成器或引入插件,仅通过内联 JavaScript 即可实现任意结构化输出——本例展示了如何将单一数值转化为清晰、对齐、多格式的诊断友好型结果,适用于嵌入式调试、教学演示或协议解析等场景。