3. Recipe解析与编译原理

Recipe在设备端通常以结构化文本(XML、JSON、YAML或厂商自定义DSL)形式存在,但设备控制器真正执行的是带时序约束的动作序列。二者之间必须经过一条完整的编译流水线:词法分析 → 语法分析 → 语义检查 → 中间表示(IR)生成 → 目标动作序列生成。理解这条流水线,是排查“Recipe加载失败”“参数不生效”“动作顺序错乱”等问题的根本前提。

一、词法分析:把字符流切成有意义的Token

词法分析器(Lexer)的职责是扫描原始字符流,按预定义规则切分为Token序列,并丢弃空白与注释。以一段简化的Recipe片段为例:

STEP 01 LOAD
  PRESSURE = 5.0 Torr
  TIME     = 12 s
END

Lexer输出形如:(KEYWORD,"STEP") (NUM,"01") (IDENT,"LOAD") (IDENT,"PRESSURE") (ASSIGN,"=") (NUM,"5.0") (UNIT,"Torr") ...。工程实现上需注意三点:一是单位与数值分离,便于后续统一换算;二是行号与列号随Token携带,否则报错时无法定位;三是关键字大小写策略要固定,多数设备DSL不区分大小写,但标识符区分,需在Lexer层明确。

二、语法分析:构建抽象语法树(AST)

语法分析器(Parser)依据文法规则将Token序列组织为AST。Recipe的典型文法可归纳为:Recipe → Step+,Step → STEP id name Setting* END,Setting → IDENT '=' Value Unit?。递归下降解析器实现简单、报错友好,适合Recipe这类结构规整的语言。

AST节点应保留源位置信息,例如StepNode{id, name, settings[], line}。语法错误如缺少END、赋值缺右值,应在此阶段抛出,并给出“第N行第M列期望END”的精确提示,而不是笼统的“解析失败”。

三、语义检查:让Recipe“讲得通”

语法正确不等于语义合法。语义检查阶段需完成四类校验:

  • 符号与作用域:引用的变量、子Recipe、设备模块是否已定义;
  • 类型与单位:压力必须为压力单位,时间必须为时间单位,禁止TIME = 5 Torr;
  • 取值域:如压力范围0.1~760 Torr、温度0~1200 ℃,越界即报错或告警;
  • 时序与依赖:抽真空必须在充气之前,某些步骤不可并行。

工程上常把语义规则做成可配置的规则表,便于不同机型复用同一编译器内核。检查结果分为Error(阻断编译)与Warning(允许编译但记录),例如“压力接近上限”可只告警。

四、中间表示生成:与设备解耦的关键层

IR是编译器的“中枢”。它把AST转换为与具体机型无关的规范化结构,通常包含:步骤序号、动作类型、参数(已统一为SI单位)、持续时间、前置条件、后置断言。IR的价值在于:同一份IR可面向不同机型生成不同动作序列,实现“一次编写、多机执行”。

IR常用有向无环图(DAG)或线性指令表两种形态。DAG便于表达并行与依赖,线性表便于顺序执行。实际工程中多采用“线性主序列 + 依赖标注”的混合形式。

五、目标动作序列生成

最后一步是把IR映射为设备可执行动作。映射依赖机型能力表:每个抽象动作(如“抽真空到5 Torr”)对应一组具体指令(开阀、启泵、读压力、闭环判断)。生成时需插入同步点、超时保护与异常分支。例如:

IR: PUMP_TO(5.0 Torr, timeout=30s)
→ OPEN(VALVE_VAC)
→ START(PUMP_P1)
→ WAIT_UNTIL(PRESSURE <= 5.0, timeout=30s)
→ CLOSE(VALVE_VAC)

若超时未达标,则跳转至异常处理序列并上报错误码。至此,一份文本Recipe才真正变成设备能“照着做”的动作序列。

Recipe编译流水线:文本 → 设备动作序列 ① 词法分析 字符流 → Token 行号/列号随行 ② 语法分析 Token → AST 递归下降解析 ③ 语义检查 符号/类型/取值域 时序依赖校验 ④ IR生成 AST → 规范化IR SI单位统一 ⑤ 动作 序列 机型映射 语法错误回退 语义错误回退 示例数据流:PRESSURE = 5.0 Torr IDENT:PRESSURE ASSIGN:= NUM:5.0 UNIT:Torr → SettingNode{key,value,unit} → IR: PUMP_TO(666.6 Pa) 语义检查规则(示例) • 类型匹配:PRESSURE 必须为压力单位 • 取值域:0.1 ≤ P ≤ 760 Torr • 时序:PUMP 必须在 GAS_IN 之前 动作序列生成(示例) OPEN(VALVE_VAC) → START(PUMP_P1) → WAIT_UNTIL(P ≤ 5.0 Torr, 30s) → 超时则跳转异常处理并上报错误码

六、工程实现中的关键取舍

第一,错误恢复策略。Lexer遇到非法字符应跳过并记录,而非立即终止,以便一次性暴露多个问题;Parser可采用同步Token(如END)恢复,避免一个错误引发连锁报错。

第二,IR的稳定性。IR一旦发布应保持向后兼容,新增字段用可选属性,避免旧机型无法解析新IR。建议为IR定义版本号,编译时校验版本匹配。

第三,机型能力表的维护。动作映射依赖设备能力描述文件,该文件应与设备固件版本绑定。若能力表缺失某动作,编译器应给出“该机型不支持PUMP_TO”的明确错误,而非静默降级。

实践建议:在开发阶段,可先实现一个“只做词法+语法+语义检查、不生成动作序列”的Dry-Run模式。这样能在不接触真实设备的情况下快速验证Recipe合法性,显著降低调试风险。Dry-Run输出的IR可打印为可读文本,便于人工复核单位换算与参数取值。
常见陷阱:单位换算若在Lexer或Parser阶段进行,会导致AST中数值含义不统一;正确做法是在IR生成阶段统一换算为SI单位,并在IR节点上保留原始单位与原始值,便于追溯与显示。

综上,Recipe编译不是简单的“读文件”,而是一条包含词法、语法、语义、IR与代码生成五个阶段的完整流水线。每一阶段都有明确的输入输出与错误处理边界。掌握这条流水线,才能在Recipe加载失败、参数异常或动作错乱时,快速定位问题发生在哪一层,并采取针对性的修复措施。