
本文详解如何通过 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"
= [ \t\n\r]*
// 支持多行输入:每个非空行独立解析并输出,用空行分隔
Lines
= line:(!("\n" / "") .)* "\n"? { return line.join(''); }✅ 使用说明:
- 将上述语法粘贴至 PEG.js Online Editor 或本地构建工具;
- 输入 (3*(5+8)) → 输出:
39 0x27 00100111- 输入多行(如 42\n(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 即可实现任意结构化输出——本例展示了如何将单一数值转化为清晰、对齐、多格式的诊断友好型结果,适用于嵌入式调试、教学演示或协议解析等场景。










