自编译指令
自编译指令是 AmritaSense 实现高级控制流的核心机制。它们在工作流渲染阶段自动展开为标准的底层节点组合,使得开发者可以像使用内置指令一样,使用自己封装的复杂编排模式。
SelfCompileInstruction
class SelfCompileInstruction(ABC):
"""Abstract base class for all self-compiling instructions.
Self-compiling instructions are expanded into NodeCompose structures during
the workflow rendering phase. This allows for compile-time optimization
and static address calculation without requiring changes to the interpreter.
"""SelfCompileInstruction 是所有自编译指令的抽象基类。它的核心职责只有一个:将高级语义映射为底层节点组合。这个映射发生在 render() 阶段,因此所有地址计算、结构展开都在执行前完成,运行时没有任何额外开销。
核心方法
extract() -> NodeCompose:将指令展开为底层节点组合。必须返回一个NodeCompose实例,该实例会被框架自动递归渲染。
设计理念
自编译指令是 AmritaSense 扩展性的基石。内置的 IF、WHILE、TRY 等全部实现自这一接口,而开发者可以通过继承它来创建自己的控制流原语——封装的是编排模式,而非具体业务逻辑。
内置自编译指令
AmritaSense 的内置指令集全部是 SelfCompileInstruction 的子类。以下仅列出它们在编译期展开的空间结构,详细语法和运行时行为请参见 第 4.5 节:内置指令集。
条件分支指令
IFClause:IF(cond, do)→[ConditionJumpNode, condition, do, NOP]ELIFClause:在IFClause基础上扩展 ELIF 链,每个 ELIF 追加一组[ConditionJumpNode, condition, do],最终 NOP 作为统一出口ELSEClause:在 IF 或 IF-ELIF 链后追加[ELSENode, else_do, NOP]
循环指令
WhileClause:WHILE(condition).ACTION(action)→[WhileNode, condition, action, CheckUpNode, NOP]DoWhileClause:DO(do).WHILE(condition)→[DONode, do, DowhileNode, condition, NOP]
异常处理指令
TryClause:展开为[TryNode, try_body, ...catch_handler_i, catch_body_i..., FinNode(可选), fin_body, NOP]。TryNode管理整条异常处理链的运行时逻辑。
子程序存储指令
SubprogramStorage(ARCHIVED_NODES的底层实现):展开为[SubprogramJumpNode, AliasNode_1, AliasNode_2, ..., NOP]。SubprogramJumpNode在正常执行流中无条件跳过整个存储区,子程序内的别名节点仅通过CALL或外部注入访问。
注意:CALL 不是自编译指令
CALL 指令对应的 CallNode 直接继承自 BaseNode,是一个普通节点。它在编译期不展开,只是作为单个节点存在于工作流数组中,执行时通过 _pre_check 解析别名并调用 pc.call_sub。这与 GOTO(JumpNode)的设计一致——两者都是“原子”跳转节点,而非自编译结构。
自定义自编译指令
实现步骤
- 继承
SelfCompileInstruction - 在
__init__中接收构造参数:这些参数决定了展开后的节点组合 - 实现
extract() -> NodeCompose:在此方法内完成所有地址计算和节点组合,返回最终的NodeCompose
简单示例
class LoggedNode(SelfCompileInstruction):
"""在执行节点前后各加一条日志"""
def __init__(self, node: BaseNode, name: str):
self._node = node
self._name = name
def extract(self) -> NodeCompose:
@Node()
def log_start():
print(f"[{self._name}] 开始执行")
@Node()
def log_end():
print(f"[{self._name}] 执行完毕")
return NodeCompose(log_start, self._node, log_end)使用:LoggedNode(process_data, "数据处理") 等价于手写 log_start >> process_data >> log_end。
设计原则
编译期 vs 运行时
- 编译期:
extract()内完成地址计算、结构展开、静态优化。所有跳转偏移量在此阶段确定 - 运行时:展开后的普通节点由解释器正常执行,没有额外的编译开销
地址计算策略
- 展开后的结构如果包含跳转(如
ConditionJumpNode),地址偏移量需要在extract()内根据节点列表长度静态计算 - 如果展开结构复用了
IF、WHILE等内置指令,地址计算由内置指令自行完成,无需手动管理
封装模式,而非封装逻辑
自定义指令应封装反复出现的编排模式(如重试、超时、条件执行),而非具体业务逻辑。业务逻辑应留在节点函数内部。
错误处理
- 参数验证放在
__init__中,尽早失败 extract()中抛出的异常会阻止工作流渲染
可组合性
自定义指令内部可以包含其他自编译指令(包括内置指令),嵌套深度没有限制。
