运行时语义

本节是**规范级**说明:同一帧如何顺序扫描子节点、entry 命中后如何 runBranch、scope 何时 push/pop。写复杂 Flow(同帧连发、嵌套 logout)时,以本节为准理解实际行为。

本节为 implement 唯一权威;与上文冲突时以本节为准。

连接状态

每 WebSocket 连接(Flow 命中后)维护:

  • scopeStack: Frame[] // 栈底为 root
  • vars: Record<string,string> // 连接级变量
  • timers: Timer[] // 每项绑定 scopeFrameId
  • phase: open | active | closing | closed

Frame = { id: string, // 稳定 nodeId(compile 分配) astNodeId: string, // 进入该帧的条件/返回 AST 节点 name?: string, // ~auth;root 无 name active: boolean, // false 时其 loop 不触发 enteredAt: number // 调试用 }

连接建立序列

  1. WS 握手完成,Flow match 命中
  2. push root Frame { active: true }
  3. 扫描 root 下 直接 --@loop 节点 → registerTimers(见「--@loop 注册时机表」)
  4. 触发 --@open(若存在):执行 open 分支(返回 + scope 进入,见 17.4)
  5. vars.$connectionId ← 分配;$now 等在读取时求值

匹配 client 文本帧 evaluateMessage(text)

  1. 若 phase=closing|closed → return
  2. capture 本帧(按 # capture)→ merge into vars
  3. 全局 !~ 候选 popMatches(优先于 entry,见步骤 5)
  4. 在栈顶 匹配上下文 子树内,源序深度优先 选取 entry 条件 entryNode:
    • 第一条 自身匹配从上下文根到该节点路径上所有消息条件均匹配-- 节点(不含 --@ 生命周期作 entry,生命周期走 §17.7)
    • !~name 的消息条件 不参与 entry 选取(仅步骤 3 popMatches)
    • 若未命中且 # on-unmatch root → 临时将匹配上下文视为 root 子树重试步骤 4
    • 若仍未命中 → 步骤 6
  5. 裁决(Normative):
    • 若 popMatches 非空 → 取源序最先 → popScope(name) → runBranch(popNode, popOnly) 或仅 pop 无返回(见 6.9.3);结束
    • 否则若 entryNode 存在 → runBranch(entryNode, enter)(§17.4);结束
    • 否则 → 未命中(# on-unmatch 等)

runBranch(node, mode?)

mode 枚举(Normative):

mode 典型调用 步骤 2 扫描发返回 push 帧 移入 scope register loop
enter entryNode、--@open 是(3) 是(4) 是(5)
scan 步骤 2b 递归子 --
popOnly popMatches(§17.3、6.9.3)

默认 enterscan / popOnly 执行步骤 2(及步骤 7 !close); push、移入子树、 register loop。

共享累加器:同一 evaluateMessage 内,enter 与其递归 scan 共用 lastReturnWithChildren(步骤 2a 源序覆盖),供 enter 步骤 4 移入 scope。

  1. anchorTime ← 本帧 enter 根 run 起点(scan/popOnly 继承同一 anchorTime,§5.1 delay 锚点)
  2. 对每个直接子节点(源序): a. 返回行 → 按 §5.1 发出;更新 delay 链;若该返回行下存在缩进子条件,更新共享 lastReturnWithChildren b. 消息条件 --… → 若本帧 也匹配runBranch(该子节点, scan)(递归) c. 生命周期 --@… → 本步不发出 client 返回(loop 在 enter 步骤 5 注册)
  3. 仅 mode=enter:push Frame { name from node 的 ~enter suffix if any, active: true };无 ~enter 仍 push 匿名帧
  4. 仅 mode=enter:移入 scope — 下一帧匹配上下文 =
    • lastReturnWithChildren 非 null → 该 返回节点 之子树
    • 否则 → node 整棵子树
  5. 仅 mode=enter:activateTimers — 对新帧匹配上下文中 已可达--@loop register(见「--@loop 注册时机表」)
  6. 若本 run(enter/scan/popOnly 任一步骤发出的返回)含 !close → 进入 closing,清空栈与 timers

生命周期 --@open 等:由 §17.7 调用 runBranch(node, enter),语义同上。

(implement 别名:executeCondition(entry) → runBranch(node, enter);递归子条件 → runBranch(node, scan)。)

popScope(name?)

  • 若 name 给定:pop 直到移除 第一个 name 匹配的帧(含其上方所有子孙帧,即该帧到栈顶整段弹出)
  • 若 name 未给定(--@close 等):clear 全部
  • 每个被 pop 的 frameId → cancel 绑定 timers
  • pop 清除 vars

--@loop 调度

onRegister(loopNode, frameId): delay0 = (# loop immediate on) ? 0 : dur schedule timer at now+delay0, period=dur onTick(loopNode): if frame(frameId).active → 发出 loop 节点下返回行(+delay);走 evaluateMessage、 push scope、递归 scan 子 -- onPop(frameId) → cancel all timers where timer.frameId == frameId or frameId 被 pop 的子孙

生命周期事件(简要)

事件 动作
--@open runBranch(openNode, enter);可带 ~session push
--@ping 全局;不 push;走 --@ping 返回或 # ping auto
--@close pop 全部;可选最后一帧;phase=closed
client text evaluateMessage