Files
datatrace/docs/add_function_comments.ps1
2026-07-14 17:02:23 +08:00

220 lines
13 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

param(
[string[]]$Files = @(
"source/icd_mapper.cpp",
"source/interface.cpp",
"source/main.cpp",
"source/mq.cpp"
)
)
$ErrorActionPreference = "Stop"
$root = Split-Path -Parent $PSScriptRoot
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
function Split-Words([string]$name) {
$text = $name -replace '^~', ''
$text = $text -creplace '([a-z0-9])([A-Z])', '$1 $2'
return ($text -replace '_', ' ').ToLowerInvariant()
}
function Brief-For([string]$name, [string]$fileName) {
$display = '“' + $name + '”'
if ($name -match '^(is|has|can|should|looks|declares|matches|contains|equals|same)') {
return "判断 $display 所表达的条件;该函数只读取输入,不负责修改业务状态。"
}
if ($name -match '^(read|load)') { return "读取或加载 $display 对应的数据,并转换为本模块后续逻辑使用的内存结构。" }
if ($name -match '^(write|save|export)') { return "$display 对应的内存数据序列化并写入目标位置。" }
if ($name -match '^(parse|decode)') { return "解析 $display 对应的文本或结构,提取经过校验的业务字段。" }
if ($name -match '^(find|search|lookup)') { return "在现有数据结构中查找 $display 对应的对象或位置。" }
if ($name -match '^(collect|gather|scan)') { return "遍历输入结构并收集 $display 所需的候选数据。" }
if ($name -match '^(build|make|create|new)') { return "构造 $display 对应的对象、消息、窗口控件或输出结构。" }
if ($name -match '^(update|refresh|populate|rebuild|reset)') { return "根据当前模型状态刷新 $display 对应的缓存或界面。" }
if ($name -match '^(run|handle|on|wndProc)') { return "处理 $display 对应的业务入口或事件,并调度下游函数完成实际工作。" }
if ($name -match '^(add|append|insert|ensure)') { return "向目标结构追加或保证存在 $display 所描述的数据。" }
if ($name -match '^(trim|lower|normalize|sanitize|safe|clean|collapse)') { return "规范化 $display 对应的字符串或标识,减少格式差异对匹配造成的影响。" }
if ($name -match '^(send|fetch|download)') { return "执行 $display 对应的外部通信,并把结果交给日志、事件或落盘流程。" }
if ($name -match '^(compare|match)') { return "比较或匹配 $display 涉及的数据,返回匹配结果或构造匹配信息。" }
if ($name -match '^(generate|reverse)') { return "根据当前配置和编辑状态生成 $display 对应的输出内容。" }
if ($name -match '^(set|apply|commit|sync)') { return "$display 对应的变更应用到模型、控件或输出文档。" }
if ($name -eq 'main') { return "命令行入口;无参数时转入 Win32 GUI有参数时执行兼容的批处理解析流程。" }
if ($fileName -eq 'interface.cpp') { return "实现界面层的 $display 功能,并在 Win32 控件与 AppState 业务模型之间传递数据。" }
if ($fileName -eq 'mq.cpp') { return "实现外部通信层的 $display 功能,服务于 HTTP、RocketMQ 或运行时文件管理。" }
if ($fileName -eq 'main.cpp') { return "实现解析/报表层的 $display 功能,参与 XML、JSON、origin 与工作簿之间的数据转换。" }
return "实现 $display 对应的模块内功能。"
}
function Detail-For([string]$fileName) {
switch ($fileName) {
'interface.cpp' { return '界面使用原生 Win32 API函数通常读取或更新 AppState并通过控件 ID、消息通知和 ListView/TreeView API 驱动交互。除 MQ 事件投递外,界面控件操作应在 GUI 线程执行。' }
'mq.cpp' { return '本文件组合使用 WinHTTP、Windows 动态库 API、RocketMQ C 接口、nlohmann::json 与 std::filesystem异常会转换为日志、错误字符串或离线 outbox。共享 MQ 会话状态由互斥锁保护。' }
'main.cpp' { return '本文件使用 tinyxml2、nlohmann::json、正则表达式和标准容器完成映射、范围计算、值比较及 Excel 2003 XML 输出;调用方应关注输入格式与路径有效性。' }
'icd_mapper.cpp' { return '本文件使用 tinyxml2 遍历 ICD/Excel XML并使用 Windows 代码页 API 兼容 GBK/GB2312文件操作采用 std::filesystem 与二进制流,失败时抛出异常交给公共入口处理。' }
default { return '函数属于工具内部实现,输入输出与副作用以参数和返回值为边界。' }
}
}
function Param-Description([string]$name) {
if ($name -match '^(app|state)$') { return '应用状态;包含窗口句柄、当前选择、预览模型和运行期标志。' }
if ($name -match 'cfg|config') { return '运行配置提供接口地址、MQ 参数、路径和当前数据模式。' }
if ($name -match '(path|file|dir|root|output|workbook|xmlPath|jsonPath|originPath)$') { return '文件或目录路径;函数按 UTF-8/Windows 路径规则读取或写入。' }
if ($name -match '^(doc|document)$') { return 'tinyxml2 文档对象;节点的生命周期由该文档管理。' }
if ($name -match '(row|cell|node|element|item|sheet)$') { return '当前处理的数据行、单元格、XML 节点或模型项。' }
if ($name -match '^(text|raw|body|value|name|key|tag|topic|label|title|search)$') { return '待解析、比较、显示或发送的 UTF-8 文本。' }
if ($name -match '^(hwnd|parent|ctl|control|list|tree)$') { return 'Win32 窗口或控件句柄;必须在有效窗口生命周期内使用。' }
if ($name -match '(index|idx|count|size|start|end|offset|col|rowIndex|id)$') { return '索引、数量、范围或偏移参数;具体边界由函数体校验。' }
if ($name -match '^(log|event|callback|fn)$') { return '回调函数;用于向界面或日志系统报告进度和结果。' }
if ($name -match '^(out|result|message|err|error)$') { return '输出参数;函数在成功或失败时写入结果或诊断信息。' }
return '调用方传入的业务参数;其用途与约束由类型、名称及函数体共同定义。'
}
function Return-Description([string]$prefix, [string]$name) {
if ($name -eq 'main' -or $prefix -match '(^|\s)int\s*$') { return '整数状态、索引或计数;入口函数以 0 表示成功。' }
if ($prefix -match '(^|\s)bool\s*$') { return '条件成立或操作成功时为 true否则为 false。' }
if ($prefix -match '(^|\s)void\s*$' -or $name.StartsWith('~')) { return $null }
return '构造、解析或查找到的结果;所有权与生命周期遵循返回类型。'
}
function Split-Parameters([string]$header) {
$open = $header.IndexOf('(')
if ($open -lt 0) { return @() }
$depth = 0
$close = -1
for ($i = $open; $i -lt $header.Length; $i++) {
if ($header[$i] -eq '(') { $depth++ }
elseif ($header[$i] -eq ')') {
$depth--
if ($depth -eq 0) { $close = $i; break }
}
}
if ($close -lt 0) { return @() }
$body = $header.Substring($open + 1, $close - $open - 1).Trim()
if ([string]::IsNullOrWhiteSpace($body) -or $body -eq 'void') { return @() }
$parts = New-Object System.Collections.Generic.List[string]
$start = 0; $angle = 0; $paren = 0; $bracket = 0; $brace = 0
for ($i = 0; $i -lt $body.Length; $i++) {
switch ($body[$i]) {
'<' { $angle++ }
'>' { if ($angle -gt 0) { $angle-- } }
'(' { $paren++ }
')' { if ($paren -gt 0) { $paren-- } }
'[' { $bracket++ }
']' { if ($bracket -gt 0) { $bracket-- } }
'{' { $brace++ }
'}' { if ($brace -gt 0) { $brace-- } }
',' {
if ($angle -eq 0 -and $paren -eq 0 -and $bracket -eq 0 -and $brace -eq 0) {
$parts.Add($body.Substring($start, $i - $start).Trim())
$start = $i + 1
}
}
}
}
$parts.Add($body.Substring($start).Trim())
return $parts
}
function Parameter-Name([string]$declaration, [int]$position) {
$decl = ($declaration -replace '=.*$', '').Trim()
if ($decl -match '\(\s*\*\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)') { return $Matches[1] }
$matches = [regex]::Matches($decl, '[A-Za-z_][A-Za-z0-9_]*')
if ($matches.Count -eq 0) { return "arg$position" }
$candidate = $matches[$matches.Count - 1].Value
if ($candidate -in @('const','unsigned','signed','long','short','int','char','bool','double','float','void')) { return "arg$position" }
return $candidate
}
function Find-Functions([string[]]$lines) {
$found = New-Object System.Collections.Generic.List[object]
for ($i = 0; $i -lt $lines.Count; $i++) {
$first = $lines[$i].Trim()
if ([string]::IsNullOrWhiteSpace($first) -or $first.StartsWith('#') -or
$first.StartsWith('//') -or $first.StartsWith('/*') -or $first.StartsWith('*')) { continue }
if ($first -match '^(if|for|while|switch|catch|return|else|do|try)\b') { continue }
if ($first.StartsWith('(') -or $first.StartsWith('!') -or $first.StartsWith(':')) { continue }
# A definition must begin on this line. Requiring '(' here prevents a closing brace
# or an ordinary statement from borrowing the next function's signature.
if (-not $first.Contains('(')) { continue }
$headerLines = New-Object System.Collections.Generic.List[string]
$hasBrace = $false
for ($j = $i; $j -lt [Math]::Min($lines.Count, $i + 16); $j++) {
$part = $lines[$j].Trim()
$headerLines.Add($part)
if ($part.Contains(';') -and -not $part.Contains('{')) { break }
if ($part.Contains('{')) { $hasBrace = $true; break }
}
if (-not $hasBrace) { continue }
$header = ($headerLines -join ' ')
$beforeBrace = $header.Substring(0, $header.IndexOf('{')).Trim()
if (-not $beforeBrace.Contains('(') -or -not $beforeBrace.Contains(')')) { continue }
if ($beforeBrace -match '\[.*\]\s*\(' -or $beforeBrace -match '\b(auto|const auto|auto&)\s+\w+\s*=') { continue }
if ($beforeBrace -match '^\s*(if|for|while|switch|catch)\s*\(') { continue }
if ($beforeBrace.Contains('(*')) { continue }
$open = $beforeBrace.IndexOf('(')
$left = $beforeBrace.Substring(0, $open).Trim()
$nameMatch = [regex]::Match($left, '([A-Za-z_~][A-Za-z0-9_:~]*)\s*$')
if (-not $nameMatch.Success) { continue }
$fullName = $nameMatch.Groups[1].Value
$name = ($fullName -split '::')[-1]
if ($name -in @('if','for','while','switch','catch')) { continue }
$prefix = $left.Substring(0, $nameMatch.Index).Trim()
if ($prefix.Contains('=')) { continue }
if ($prefix -notmatch '[A-Za-z_]') { continue }
# Ordinary calls such as ListView_SetItemText(...) also have no return-type prefix.
# Constructors have no return-type prefix and resemble ordinary calls, so this
# conservative scanner leaves them to a short manual review after generation.
if ([string]::IsNullOrWhiteSpace($prefix) -and -not $name.StartsWith('~')) { continue }
if ($prefix.EndsWith('.') -or $prefix.EndsWith('->')) { continue }
$found.Add([pscustomobject]@{ Index=$i; Header=$beforeBrace; Name=$name; Prefix=$prefix })
}
return $found
}
foreach ($relative in $Files) {
$path = Join-Path $root $relative
if (-not (Test-Path -LiteralPath $path)) { throw "Missing source file: $path" }
$text = [IO.File]::ReadAllText($path, [Text.Encoding]::UTF8)
$newline = if ($text.Contains("`r`n")) { "`r`n" } else { "`n" }
$lines = [regex]::Split($text, "\r?\n")
$functions = @(Find-Functions $lines)
$byIndex = @{}
foreach ($fn in $functions) { $byIndex[$fn.Index] = $fn }
$out = New-Object System.Collections.Generic.List[string]
$fileName = [IO.Path]::GetFileName($path)
$inserted = 0
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($byIndex.ContainsKey($i)) {
$fn = $byIndex[$i]
$already = $false
# A function with many parameters can have a fairly long generated Doxygen
# block. Search far enough back that a second run remains idempotent.
for ($k = [Math]::Max(0, $i - 64); $k -lt $i; $k++) {
if ($lines[$k] -like "*Function: $($fn.Name)*") { $already = $true; break }
}
if (-not $already) {
$indent = ([regex]::Match($lines[$i], '^\s*')).Value
$out.Add($indent + '/**')
$out.Add($indent + " * Function: $($fn.Name)")
$out.Add($indent + ' *')
$out.Add($indent + ' * @brief ' + (Brief-For $fn.Name $fileName))
$out.Add($indent + ' * @details ' + (Detail-For $fileName))
$position = 1
foreach ($param in @(Split-Parameters $fn.Header)) {
$paramName = Parameter-Name $param $position
$out.Add($indent + " * @param $paramName " + (Param-Description $paramName))
$position++
}
$returnText = Return-Description $fn.Prefix $fn.Name
if ($null -ne $returnText) { $out.Add($indent + ' * @return ' + $returnText) }
$out.Add($indent + ' */')
$inserted++
}
}
$out.Add($lines[$i])
}
[IO.File]::WriteAllText($path, ($out -join $newline), $utf8NoBom)
Write-Host "[OK] ${relative}: detected=$($functions.Count), inserted=$inserted"
}