# CodePlanet AST 逻辑架构（维护参考）

> **版本**：2026-05-21（P5 遗留清理 + Lexer/Catalog 统一）  
> **状态**：F5/F6/F10/Shift+F11 调试主路径；最小 Qt Test 回归  
> **维护要求**：改动 `src/core/runtime/ast/`、`ControlLogiLib` 或调试 UI 时须同步更新本文档。

---

## 1. 架构概览

```
CodeEditorWidget
  ├─ gutter 断点（红色圆点，0-based 行号）
  ├─ F5 Continue / F6 Step Over / F10 Step Into / Shift+F11 Step Out
  └─ Ctrl+B BuildProgram 门禁
        │
        ▼
ControlLogiLib → ast::AstRuntime
        ├─ Lexer（**FunctionCatalog::keywordTokenType**）→ Parser → Program (AST + FunctionDecl 表)
        ├─ AstInterpreter（帧栈执行）
        │     ├─ ExprParser → ExprEvaluator（表达式 **单轨 AST**）
        │     ├─ 用户 function 调用栈 + 局部作用域
        │     └─ switch 控制帧 + case 块帧（可单步）
        └─ FunctionDispatcher（插件库函数，evaluateExpression → ExprEvaluator）
```

`parameterparser.cpp` 仅保留 **参数读写 facade**（`ReadParameter` / `WriteParameter` / `Create*` / `DeleteParameter`）。行扫描 `FunctionPars`、`ReturnValueUnit`、`CodeSource` **P5 已删除**。**控制流**仅走 AST。

### FunctionCatalog 数据流（P2 + P5 Lexer 统一）

```
FunctionLib.ini (FileService)
       │
       ▼
FunctionCatalog::instance()  ← Application::initializeServices
       ├─► FunctionLib（CodeEditor 函数元数据 / 签名）
       ├─► YsHighlightScanner（控制流/类型/库名高亮）
       ├─► CodeEditor::loadFunctionLibrary / updateCompletionList
       ├─► Lexer::identifierOrKeyword → keywordTokenType（end_* 不映射）
       └─► Application::validateFunctionCatalogPlugins（对比 PluginManager）
```

- 编辑器与运行时关键字 **单一来源**：`FunctionLib.ini` → `FunctionCatalog`
- `end_if` / `end_for` / `end_switch` 仅编辑器补全/高亮；Parser 仍用 `{`/`}` 块语法

---

## 2. 模块与文件

| 文件 | 职责 |
|------|------|
| `ast/Lexer.cpp` | 词法分析；关键字来自 `FunctionCatalog::keywordTokenType` |
| `ast/Parser.cpp` | 语法分析 → AST；顶层 `function` 声明 |
| `ast/ExprParser.cpp` | 表达式 → `ExprNode` 树 |
| `ast/ExprEvaluator.cpp` | 表达式求值；`evaluateExpressionAst` 供插件/legacy |
| `ast/AstInterpreter.cpp` | 执行引擎、调试帧栈、断点、Step Into/Out |
| `ast/AstRuntime.cpp` | 对外门面 |
| `logic/controllogilib.cpp` | 主线程编排、冲突守卫、DB 会话 |
| `logic/RuntimeWorker.cpp` | Ops 运行线程、`AstRuntime` 串行执行（F5 调试） |
| `logic/MainTaskWorker.cpp` / `SubTaskWorker.cpp` | **R2** 产线主/子任务循环（`RuntimeOrchestrator` 调度） |
| `core/runtime/MainTaskTimerService.cpp` | **R2** 主任务 timer API；`MainTaskWorker` 每 tick 调用 `tick()` |
| `core/functions/FunctionCatalog.cpp` | INI 单一解析：函数 CSV + 控制流/高亮/插件元数据 |
| `core/functions/basefunction.cpp` | `FunctionLib` facade，委托 Catalog |

---

## 3. 语句与节点

| 节点 | 语法 |
|------|------|
| `FunctionDeclStmt` | `function name(a, b) { ... }` |
| `ReturnStmt` | `return;` / `return expr;` |
| `VarDeclStmt` | `int/static/array` |
| `IfStmt` / `ForStmt` / `WhileStmt` / `SwitchStmt` | 控制流 |
| `SwitchStmt::CaseArm` | 每 case/default 含独立 `Block`（P1） |

表达式：`Expr { source, ast }`，由 `makeExpr()` 在解析时填充。

---

## 4. 变量作用域

| 类型 | 写法 | 存储（R1） |
|------|------|------------|
| 局部 | `int x = 0;` | `sourceParameterL[1]` 或 function 内 `m_localScopes` |
| 任务内 static | `static int s = 0;` | `RuntimeWorker` 会话 `QHash`（不进 scope 0） |
| 跨任务 global | `global int g = 0;` | `ProjectGlobalStore`（按 yslib 路径） |
| import | `import "path.ys";` | **R2**：`executeImport` 合并模块 `functions`；静态 `analyzeProjectImports` + 跨模块 call（YS405） |
| `#include` | `#include "rel/path.ys"` | **R2**：`AppPaths::resolveIncludeRelative(path, sourceFilePath)` 相对脚本目录 |
| 函数参数 | `function f(a,b)` | 调用时压入 `m_localScopes` 栈 |

---

## 5. 调试模型（VSCode 对齐）

| 快捷键 | DebugMode | 行为 |
|--------|-----------|------|
| **F5** | Continue | 运行至结束、断点或 Stop |
| **F5**（运行中） | — | `requestStop()` 终止 |
| **F6** | Step Over | 前进 **一行源码**（含空行/注释行）；若该行有语句则执行一条 |
| **F10** | Step Into | 单步；遇**用户 function** 时进入函数体首行；**插件函数**（`print` 等，`FunctionCatalog` 注册）整句执行不进入 |
| **Shift+F11** | Step Out | 运行至当前 function 返回 |

**箭头**：始终指向 **待运行行**（`m_nextRunLine`，1-based）。F5 连续运行时箭头跟随当前执行行；F6 每按一次前进一行源码。

### 5.1 断点（P3）

- **设置**：行号 gutter 第二列（15–30px）**左键**添加红色圆点
- **取消**：再次左键红色圆点
- **行号**：编辑器内 **0-based**（`blockNumber`）；AST 源码 **1-based**，在 `UpdateCodeStepLine` 转换
- **生效**：F5/F6/F10/Shift+F11 运行前 `setBreakpoints()` 注入解释器

### 5.2 执行帧栈

| FrameKind | 说明 |
|-----------|------|
| `Block` | 普通块 / case 块 / function 体 |
| `ForLoop` / `WhileLoop` | 循环体，块结束自动 update/条件 |
| `SwitchControl` | switch 控制；case 块结束后 fall-through |

### 5.3 行号约定

- AST `SourceSpan.line`：**1-based**
- `CodeEditor::UpdateCodeStepLine(line)`：内部转为 `debugArrowLine = line - 1`
- 解释器 `currentLine()` 返回 **待运行行**（非上一句已执行行）

**箭头**：始终指向 **待运行行**（`m_nextRunLine`，1-based）。F2/F3 重置后箭头对准 **第 1 行**。F5 连续运行时箭头跟随当前执行行；F6/F10 每按一次前进一步（**物理源码行优先**，仅 `line == stmtLine` 时执行语句）。

### 5.4 单步指针（`stepOnce`）

| 场景 | 箭头行为 |
|------|----------|
| 普通语句执行后 | 指向 `lastExecutedLine + 1`（逐行推进） |
| 空行 / 注释 | 仅前进一行，不执行 |
| 函数声明扫描（未进入该函数） | 在 `function` 头行单步跳到函数体闭合 `}` 行 |
| 顶层 `foo();`（ExprStmt）F6 | 进入 `foo` 的 `function` 头行逐步执行 |
| 表达式内嵌用户函数调用 F6 | Step Over，不进入被调函数 |
| F10 进入用户函数 | 在 `function` 头行暂停 |
| 函数返回（调试模式） | 暂停在 `executingStatementLineForFrame(caller) + 1` |
| `for/while` 体结束于 **`}`** | 执行迭代尾步（update + 条件）；**条件仍真** → 箭头回到 **`for(...){` / `while(...){` 行** |
| 循环条件为假 | 弹出循环帧，箭头指向 `}` 下一行 |
| `break` / `continue` | 弹出至最近 loop/switch 控制帧；`continue` 将 index 置为体末尾以触发尾步 |

### 5.5 循环与副作用

- `for` 的 **update**、`while/for` 体内的 **`i = i + 1`** 等表达式语句经 **`executeSideEffect`** 执行赋值（`executeExprStmt` 识别 `=`/`++`/`--`）
- **F6 单步**：`for/while` 体执行完后，在 **`}` 行**再按 F6 执行迭代尾步（update + 条件判断）
- `executeNext` 每次最多执行 **一条**语句，不在一次 F6 内连跳多次循环

### 5.6 用户自定义 function 与插件函数（F10）

- **用户 function**（`function add(...) { ... }`，`m_functions` 表）：F6/F5 **跑完**；F10 **停在函数体第一条语句**（`m_stepIntoPauseRequested`），赋值/return 等宿主语句尚未完成
- **插件 function**（`FunctionCatalog` 注册、`print` 等）：F10 **与 F6 相同**，整句执行，不进入 DLL 实现
- `int c = add(10, 20);`：F10 在 `add` 入口暂停；F6 一次执行完 `add` 并写入 `c`
- 插件函数仍走 `FunctionDispatcher`；未找到时提示 `Function not found:`

### 5.7 Call Stack 面板

- **入口**：调试工具栏 **「栈」** 按钮 → `QStackedWidget` 第 3 页
- **列**：`#` / `名称` / `行号` / `类型`（function / block / for / while）
- **数据源**：`AstInterpreter::debugFrameSnapshot()` → `RuntimeWorker::stackChanged` → `CodeEditorWidget::updateDebugStack`
- **当前帧**：栈顶行浅蓝底 + 粗体
- **双击**：编辑器跳转到对应行（只读，不改变调试箭头/运行状态）

### 5.7 线程编排（P0）

脚本执行与调试 **不再** 使用 `QtConcurrent` 线程池，改为 **专用运行线程** 串行化所有 `AstRuntime` mutate。

**三层线程模型**：

| 线程 | 对象 | 职责 |
|------|------|------|
| 主线程 | `ControlLogiLib` | UI 请求、DB 会话、`UpdateCodeStepLine` / `UpdatePara` / `SendDebugMessage` |
| 运行线程 | `RuntimeWorker` + `ast::AstRuntime` | `loadSource`、`runContinue`、`step*`、`resetDebug`、`clearGlobals` |
| 全局参数线程 | `sourceParameterL[0]`（`SourceParameter(true)`） | 全局/static 参数读写删（QueuedConnection） |

**请求路径**：主线程 `RunCodeEditor` / `RunStep*` / `Reset*` → `worker*` 信号 → `Qt::QueuedConnection` → `RuntimeWorker` 槽；worker 完成后通过 `runFinished` / `stepFinished` / `resetFinished` 等信号回到主线程。

**冲突策略**：

- F5 运行中再 F5 → `requestStop()`，等 `runFinished`，不启动第二个 full run
- F5 运行中 F6/F10/Shift+F11 → 忽略并提示「运行中，请先停止 (F5)」
- 连续 F6 → `m_stepInProgress` 门卫 + `m_debugStepToken` 丢弃过期行更新

**调试 UI 回传**：

- `postDebugLineUpdate`：`QueuedConnection` + stepToken，丢弃过期箭头
- **P4 调试日志单路径**：所有调试文本经 `ControlLogiLib::publishDebugMessage(msg, lineNo)` → `DebugLogService::log`（SQLite，需活跃 debug session）+ `emit SendDebugMessage(msg)` → `MainWindow` → `ReceiverDebugMessage`
- 生产者：`Init_DebugMessage` 全局回调（`parameterparser` / `FunctionDispatcher`）、`ReviceMessage`（参数 `SendMessage`）、`RuntimeWorker::debugMessage`（含 `(at line:col)` 行号解析）、逻辑层直接提示（停止/改参等）
- 全局 `SendDebugMessage` 回调已 **deprecated**；新代码勿绕过 `publishDebugMessage`

```mermaid
flowchart LR
  UI[MainThread_UI] --> CL[ControlLogiLib]
  CL -->|QueuedConnection| RW[RuntimeWorker]
  RW --> AR[AstRuntime]
  RW -->|signals_Queued| CL
  AR -->|Write_Delete_Clear| SP0[GlobalParameterThread]
```

**断点（P3 后）**：命中断点时**尚未执行**该行，红色箭头指向即将执行的语句；F5 再次按下从当前位置 **Continue**（不重置会话），F2/F3 复位调试状态。

- `finalizeDebugSession`：F5 `runFinished` 后于主线程写 DB

**Debug 日志 UI（P4）**：

```mermaid
flowchart LR
  PP[parameterparser_FunctionDispatcher] --> Init[Init_DebugMessage]
  RW[RuntimeWorker_debugMessage] --> Pub[publishDebugMessage]
  Init --> Pub
  Rev[ReviceMessage] --> Pub
  Pub --> DLS[DebugLogService_log]
  Pub --> Sig[SendDebugMessage]
  Sig -->|QueuedConnection| RCV[ReceiverDebugMessage]
  RCV --> DE[m_debugEdit_append]
```

---

## 6. 表达式 AST（P2）

`ExprParser` 支持：`+ - * / %`、比较、逻辑、`()`、数组下标、成员（`.L`/`.Length`/`.Rad` 等）、`++`/`--`、函数调用。

### 6.1 `+` 运算符（JavaScript/C# 风格）

| 左/右类型 | 结果 |
|-----------|------|
| 任一侧为 `string` | 文本拼接（`int`→`"1"`，`float`→`"1.0"` 一位小数） |
| 均为数值 | 数值相加；有 `float` 则结果为 `float`（如 `int+float`→`2.0`） |
| 结合性 | 左结合，`(i+f)+s` 与 `i+(f+s)` 按 AST 树求值 |

`print(expr)` 由 AST 先求值再输出；**不再**对参数二次调用 `ReturnValue`。

表达式求值数据流（单轨）：

```mermaid
flowchart LR
  makeExpr --> ExprParser --> ExprNode
  ExprNode --> ExprEvaluator
  ExprEvaluator --> ReadParameter
  ExprEvaluator --> WriteParameter
  ExprEvaluator --> FunctionDispatcher
  FunctionDispatcher_plugins[插件 evaluateExpression] --> evaluateExpressionAst
  evaluateExpressionAst --> ExprEvaluator
```

1. `makeExpr` / `ExprParser::parse` 构建 `ExprNode` 树  
2. `ExprEvaluator` 求值（无 `ReturnValue` fallback；失败返回 `%Err%`）  
3. 插件侧 `FunctionCallContext::evaluateExpression` → `evaluateExpressionAst`  
4. 插件 `evaluateExpression` → `evaluateExpressionAst`（无 `ReturnValue` 路径）

---

## 7. P5 遗留清理（2026-05-21）

| 项 | 处置 |
|----|------|
| `FunctionPars` / `ReturnValueUnit` | 自 `parameterparser.cpp` 物理删除（src 无调用） |
| `CodeSource` / `codeSourcesL` | 删除；`#include` 仅 `AstInterpreter::executeInclude` |
| Lexer 关键字 | 删除 `kKeywords[]`；改查 `FunctionCatalog::keywordTokenType` |
| `modifyPara` | 全局参数（id=0）经 `BlockingQueuedConnection` 在 `SourceParameter` 所属线程读写 |
| `deletePara` | 参数面板 → `DeleteParameter` → `updateParameters` |
| 插件调试 | `FunctionDispatcher::setDebugMessageSink` → `publishDebugMessage`；全局 `SendDebugMessage` 仅 `Init_DebugMessage` deprecated 转发 |
| 自动化 | `tests/` Qt Test + `scripts/run_all_checks.ps1` |

局部参数（id=1）与 worker 并发写入仍为已知限制；全局参数线程亲和性已在 P5 闭环。

---

## 8. 用户自定义函数（P4）

```ys
function add(int a, int b) {
    int sum = a + b;
    print(sum);
    return sum;
}
```

- 解析：`Program.functions` + `Parser.parseFunctionDecl`
- 调用：优先查 `m_functions`，否则 `FunctionDispatcher`
- **Step Over**：一次执行完整个 function 体
- **Step Into**：停在 function 第一行
- **Step Out**：运行至 `return` 或块结束

插件函数（`print` 等）不可 Step Into。

---

## 8. 测试脚本

`examples/ast_debug.ys` — 覆盖 if/for/while/switch/function/static/print。  
自动化：`tests/test_script_analyzer.cpp` 对全文件 `ScriptAnalyzer::analyze` 期望 0 error。

---

## 9. 已知限制

- 插件函数无 Step Into
- Build 错误高亮仍依赖 `buildprogram.cpp` 字符串解析 `(at line:col)`（结构化 `Diagnostic` 待 P4+ 技术债）
- `BuildProgram`（Ctrl+B）已改为 **`ScriptAnalyzer` 结构化诊断**（词法/语法/语义）；问题面板四列，编辑器内波浪下划线；F5/F6 预检静默、有错才切面板

---

## 10. 扩展指南

1. **新语句**：`AstNodes.h` → `Parser` → `AstInterpreter` → 更新本文档  
2. **新库函数**：插件 + `FunctionLib.ini`（AST 无改动）  
3. **新表达式运算符**：`ExprParser` + `ExprEvaluator`  

---

## 11. R2 产线 Runtime（2026-05-24）

```
CodePlanetLauncher → CodePlanetRuntime.exe
  ParentProcessGuard + --project <yslib>
  RuntimeOrchestrator
    ├─ MainTaskWorker（QTimer loopIntervalMs → runContinue 一轮 + timerService.tick()）
    └─ SubTaskWorker[]（simulateTrigger 队列；R3 接硬触发）
  RuntimeMainWindow（日志 / global 表 / processGroups 树，150ms 刷新）
```

- **import 运行时**：`AstInterpreter::executeImport` 解析路径同 `ScriptAnalyzer::resolveImportPath`；仅合并 `functions`；同名冲突运行期报错
- **global 跨脚本**：每变量仅在一个脚本 `global` 声明；`ProjectValidator` + `analyzeProjectGlobals` → YS408；其他脚本经 `ProjectGlobalStore` 读写
- **timer**：`FunctionLib.ini` 登记 `timer_*`；`FunctionDispatcher` 内置 dispatch（非插件）；子任务脚本静态 YS406
- **Ops 与产线并存**：`RuntimeWorker` 不变；产线用 `MainTaskWorker`/`SubTaskWorker`

---
