CodePlanet AST 逻辑架构(维护参考)
下载原文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
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:F5runFinished后于主线程写 DB
Debug 日志 UI(P4):
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。
表达式求值数据流(单轨):
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)
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
---