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才真正变成设备能“照着做”的动作序列。
六、工程实现中的关键取舍
第一,错误恢复策略。Lexer遇到非法字符应跳过并记录,而非立即终止,以便一次性暴露多个问题;Parser可采用同步Token(如END)恢复,避免一个错误引发连锁报错。
第二,IR的稳定性。IR一旦发布应保持向后兼容,新增字段用可选属性,避免旧机型无法解析新IR。建议为IR定义版本号,编译时校验版本匹配。
第三,机型能力表的维护。动作映射依赖设备能力描述文件,该文件应与设备固件版本绑定。若能力表缺失某动作,编译器应给出“该机型不支持PUMP_TO”的明确错误,而非静默降级。
综上,Recipe编译不是简单的“读文件”,而是一条包含词法、语法、语义、IR与代码生成五个阶段的完整流水线。每一阶段都有明确的输入输出与错误处理边界。掌握这条流水线,才能在Recipe加载失败、参数异常或动作错乱时,快速定位问题发生在哪一层,并采取针对性的修复措施。