rebar3 服务器

This commit is contained in:
lwt
2026-05-19 11:30:21 +08:00
parent aeb18acee0
commit 03fd2da480
5187 changed files with 3670646 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
$ErrorActionPreference = 'Stop'
$root = 'apps/game_server/src/behavior_tree'
$changed = 0
Get-ChildItem $root -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
if ($text -notmatch '-include\("bt_game\.hrl"\)\.' -and
($text -match '\btree_node\(\)' -or $text -match '#tree_node\{' -or $text -match '\bblackboard\(\)' -or $text -match '\bbt_status\(\)')) {
$rx = [regex]::new('(^-module\([^)]+\)\.\s*)', [Text.RegularExpressions.RegexOptions]::Multiline)
$new = $rx.Replace($text, "`$1`r`n-include(`"bt_game.hrl`").`r`n", 1)
if ($new -ne $text) {
if ($_.IsReadOnly) { $_.IsReadOnly = $false }
[IO.File]::WriteAllText($path, $new)
$changed++
}
}
}
"changed_files=$changed"
@@ -0,0 +1,56 @@
$ErrorActionPreference = 'Stop'
$hrls = Get-ChildItem apps -Recurse -Filter '*_records.hrl' |
Where-Object { $_.FullName -match '\\include\\auto_records\\' }
$changed = 0
function Insert-Include($lines, $includeLine) {
$lastInclude = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^\s*-include(_lib)?\(') {
$lastInclude = $i
}
}
if ($lastInclude -ge 0) {
$before = $lines[0..$lastInclude]
$after = @()
if ($lastInclude + 1 -lt $lines.Count) { $after = $lines[($lastInclude + 1)..($lines.Count - 1)] }
return @($before + $includeLine + $after)
}
$moduleLine = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^\s*-module\(') {
$moduleLine = $i
break
}
}
if ($moduleLine -ge 0) {
$before = $lines[0..$moduleLine]
$after = @()
if ($moduleLine + 1 -lt $lines.Count) { $after = $lines[($moduleLine + 1)..($lines.Count - 1)] }
return @($before + '' + $includeLine + $after)
}
return @($includeLine + $lines)
}
foreach ($hrl in $hrls) {
$module = $hrl.BaseName -replace '_records$', ''
$appRoot = Split-Path (Split-Path (Split-Path $hrl.FullName -Parent) -Parent) -Parent
$srcs = Get-ChildItem $appRoot -Recurse -Filter "$module.erl" |
Where-Object { $_.FullName -match '\\(src|db_patch|db_merge|db_clear)\\' }
foreach ($src in $srcs) {
$include = "-include(""auto_records/$($hrl.Name)"")."
$text = [IO.File]::ReadAllText($src.FullName)
if ($text.Contains($include)) { continue }
$lines = [System.Collections.Generic.List[string]]::new()
$text -split "`r?`n", -1 | ForEach-Object { [void]$lines.Add($_) }
$newLines = Insert-Include $lines $include
if ($src.IsReadOnly) { $src.IsReadOnly = $false }
[IO.File]::WriteAllText($src.FullName, ($newLines -join "`r`n"))
$changed++
}
}
"changed_files=$changed"
@@ -0,0 +1,100 @@
$ErrorActionPreference = 'Stop'
$cacheCommon = 'apps/game_server/include/auto_gen/cache/cache_common.hrl'
$cacheErlDir = 'apps/game_server/cache'
if (-not (Test-Path $cacheCommon)) {
throw "Missing cache_common.hrl: $cacheCommon"
}
if (-not (Test-Path $cacheErlDir)) {
throw "Missing cache erl dir: $cacheErlDir"
}
$macroToServerType = @{
'ZONE_SERVER_CACHE_LIST' = 'zone_server'
'CENTER_SERVER_CACHE_LIST' = 'center_server'
'CROSS_SERVER_CACHE_LIST' = 'cross_server'
'GAME_SERVER_CACHE_LIST' = 'game_server'
}
$text = [IO.File]::ReadAllText((Resolve-Path $cacheCommon).Path)
$tableServers = @{}
foreach ($macro in $macroToServerType.Keys) {
$serverType = $macroToServerType[$macro]
$m = [regex]::Match($text, "(?ms)-define\($macro,\s*\[(?<body>.*?)\]\)\.")
if (-not $m.Success) { continue }
foreach ($entry in [regex]::Matches($m.Groups['body'].Value, '\{\s*(?<table>[a-zA-Z][a-zA-Z0-9_]*)\s*,\s*[a-zA-Z][a-zA-Z0-9_]*\s*\}')) {
$table = $entry.Groups['table'].Value
if (-not $tableServers.ContainsKey($table)) {
$tableServers[$table] = [System.Collections.Generic.List[string]]::new()
}
if (-not $tableServers[$table].Contains($serverType)) {
[void]$tableServers[$table].Add($serverType)
}
}
}
function Format-ServerTypes($items) {
$order = @('game_server', 'center_server', 'cross_server', 'zone_server')
$sorted = @()
foreach ($x in $order) {
if ($items.Contains($x)) { $sorted += $x }
}
foreach ($x in $items) {
if ($sorted -notcontains $x) { $sorted += $x }
}
return '[' + ($sorted -join ',') + ']'
}
$changed = 0
$updatedTables = 0
$missing = [System.Collections.Generic.List[string]]::new()
Get-ChildItem $cacheErlDir -Filter 'table_*.erl' | ForEach-Object {
$path = $_.FullName
$content = [IO.File]::ReadAllText($path)
$newContent = $content
$tableNames = [regex]::Matches($content, 'tab_name\s*=\s*(?<table>[a-zA-Z][a-zA-Z0-9_]*)') |
ForEach-Object { $_.Groups['table'].Value } |
Select-Object -Unique
foreach ($table in $tableNames) {
if (-not $tableServers.ContainsKey($table)) {
[void]$missing.Add($table)
$serverTypes = '[]'
} else {
$serverTypes = Format-ServerTypes $tableServers[$table]
}
$pattern = "(?ms)(#table\{\s*.*?tab_name\s*=\s*$table\b.*?prim_key_list\s*=\s*\[[^\]]*\])(?<rest>.*?)(?=\r?\n\s*\}\s*(?:,|\r?\n\s*\]))"
$newContent = [regex]::Replace($newContent, $pattern, {
param($match)
$head = $match.Groups[1].Value
$rest = $match.Groups['rest'].Value
if ($rest -match '(?m)^\s*,\s*server_types\s*=') {
$rest = [regex]::Replace($rest, '(?m)^(\s*,\s*server_types\s*=\s*)\[[^\]]*\]', "`${1}$serverTypes")
} else {
$rest = $rest + "`r`n , server_types = $serverTypes"
}
$script:updatedTables++
return $head + $rest
}, 1)
}
if ($newContent -ne $content) {
if ($_.IsReadOnly) { $_.IsReadOnly = $false }
[IO.File]::WriteAllText($path, $newContent)
$changed++
}
}
"server_type_tables=$($tableServers.Count)"
"changed_files=$changed"
"updated_tables=$updatedTables"
"missing_tables=$($missing.Count)"
if ($missing.Count -gt 0) {
$missing | Sort-Object -Unique | ForEach-Object { "MISSING $_" }
}
@@ -0,0 +1,14 @@
#!/usr/bin/env escript
%%! -noshell
main(_) ->
Files = filelib:wildcard("apps/game_server/cache_recovered_grouped/*.table"),
Bad = [{F, E} || F <- Files, {error, E} <- [file:consult(F)]],
io:format("files=~p bad_count=~p~n", [length(Files), length(Bad)]),
case Bad of
[] ->
halt(0);
_ ->
io:format("~p~n", [Bad]),
halt(1)
end.
@@ -0,0 +1,14 @@
#!/usr/bin/env escript
%%! -noshell
main(_) ->
Files = filelib:wildcard("apps/game_server/cache_recovered/*.table"),
Bad = [{F, E} || F <- Files, {error, E} <- [file:consult(F)]],
io:format("files=~p bad_count=~p~n", [length(Files), length(Bad)]),
case Bad of
[] ->
halt(0);
_ ->
io:format("~p~n", [Bad]),
halt(1)
end.
@@ -0,0 +1,147 @@
$ErrorActionPreference = 'Stop'
$sourceDir = 'apps/game_server/cache_recovered_grouped'
$hrlDir = 'apps/game_server/include/auto_gen/cache'
$outDir = 'apps/game_server/cache'
if (-not (Test-Path $sourceDir)) {
throw "Missing source dir: $sourceDir"
}
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
function Escape-BinaryString([string]$Text) {
if ($null -eq $Text -or $Text -eq '') { return '<<>>' }
$escaped = $Text.Replace('\', '\\').Replace('"', '\"')
return "<<`"$escaped`"/utf8>>"
}
function Read-HrlComments([string]$Table) {
$comments = @{}
$path = Join-Path $hrlDir "$Table`_c.hrl"
if (-not (Test-Path $path)) { return $comments }
$text = [IO.File]::ReadAllText((Resolve-Path $path).Path)
$pattern = '(?m)^\s*(?<field>''_id''|[a-zA-Z][a-zA-Z0-9_]*)\s*=\s*.*?\s*%%\s*(?<comment>.*?)(?=\r?\n\s*(?:''_id''|[a-zA-Z][a-zA-Z0-9_]*)\s*=|\r?\n\s*\}\)\.|\z)'
foreach ($m in [regex]::Matches($text, $pattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)) {
$field = $m.Groups['field'].Value.Trim("'")
if ($field -eq '_id') { continue }
$comment = $m.Groups['comment'].Value
$comment = [regex]::Replace($comment, '\s+', ' ').Trim()
if ($comment -ne '') {
$comments[$field] = $comment
}
}
return $comments
}
function Read-TableTerms([string]$Path) {
$text = [IO.File]::ReadAllText($Path)
$terms = [System.Collections.Generic.List[object]]::new()
$pattern = '(?ms)^\{(?<table>[a-zA-Z][a-zA-Z0-9_]*),\s*\[\s*(?<fields>.*?)\s*\],\s*(?<keys>\[[^\]]*\]),\s*(?<opts>#\{.*?\})\s*\}\.'
foreach ($m in [regex]::Matches($text, $pattern)) {
$table = $m.Groups['table'].Value
$fieldsText = $m.Groups['fields'].Value
$keys = ($m.Groups['keys'].Value -replace '\s+', '')
$opts = ($m.Groups['opts'].Value -replace '\s+', '')
$fields = [System.Collections.Generic.List[object]]::new()
$fieldPattern = '\{\s*(?<name>[a-zA-Z][a-zA-Z0-9_]*)\s*,\s*(?<type>[a-zA-Z][a-zA-Z0-9_]*)\s*,\s*(?<len>\d+)\s*,\s*"(?<comment>(?:\\.|[^"\\])*)"\s*,\s*(?<sync>true|false)\s*(?:,\s*(?<default>.*?))?\s*\}\s*,?'
foreach ($fm in [regex]::Matches($fieldsText, $fieldPattern, [System.Text.RegularExpressions.RegexOptions]::Singleline)) {
[void]$fields.Add([pscustomobject]@{
Name = $fm.Groups['name'].Value
Type = $fm.Groups['type'].Value
Len = $fm.Groups['len'].Value
Comment = $fm.Groups['comment'].Value.Replace('\"', '"')
Sync = $fm.Groups['sync'].Value
})
}
[void]$terms.Add([pscustomobject]@{
Table = $table
Fields = $fields
Keys = $keys
Opts = $opts
})
}
return $terms
}
function Opt-Value([string]$Opts, [string]$Key) {
$m = [regex]::Match($Opts, "$Key=>(?<value>(\[[^\]]*\]|[a-zA-Z0-9_]+))")
if ($m.Success) { return $m.Groups['value'].Value }
return $null
}
function Get-ServerTypes([string]$Table) {
if ($Table.StartsWith('center_')) {
return '[center_server]'
}
if ($Table.StartsWith('cross_') -or $Table -match '_cross($|_)') {
return '[cross_server]'
}
return '[game_server]'
}
function Write-Module([string]$GroupName, $Terms) {
$module = "table_$GroupName"
$lines = [System.Collections.Generic.List[string]]::new()
[void]$lines.Add("-module($module).")
[void]$lines.Add("")
[void]$lines.Add("-export([list/0]).")
[void]$lines.Add("")
[void]$lines.Add('-include("table.hrl").')
[void]$lines.Add("")
[void]$lines.Add("list() ->")
[void]$lines.Add(" [")
for ($ti = 0; $ti -lt $Terms.Count; $ti++) {
$term = $Terms[$ti]
$comments = Read-HrlComments $term.Table
[void]$lines.Add(" #table{")
[void]$lines.Add(" tab_name = $($term.Table),")
[void]$lines.Add(" field_list = [")
for ($fi = 0; $fi -lt $term.Fields.Count; $fi++) {
$field = $term.Fields[$fi]
$comment = if ($comments.ContainsKey($field.Name)) { $comments[$field.Name] } else { $field.Comment }
$common = Escape-BinaryString $comment
$comma = if ($fi -lt $term.Fields.Count - 1) { "," } else { "" }
[void]$lines.Add(" #field{name = $($field.Name), type = $($field.Type), len = $($field.Len), common = $common, sync = $($field.Sync)}$comma")
}
[void]$lines.Add(" ],")
[void]$lines.Add(" prim_key_list = $($term.Keys)")
$indexList = Opt-Value $term.Opts 'indexes'
$uidField = Opt-Value $term.Opts 'uid_field'
$preLoad = Opt-Value $term.Opts 'pre_load'
$clearAll = Opt-Value $term.Opts 'clear_all'
$mergeType = Opt-Value $term.Opts 'merge_type'
if ($indexList) { [void]$lines.Add(" , index_list = $indexList") }
if ($uidField) { [void]$lines.Add(" , uniq_id_field = $uidField") }
if ($preLoad) { [void]$lines.Add(" , pre_load = $preLoad") }
if ($clearAll) { [void]$lines.Add(" , all_clear = $clearAll") }
if ($mergeType) { [void]$lines.Add(" , merge_type = $mergeType") }
[void]$lines.Add(" , server_types = $(Get-ServerTypes $term.Table)")
$suffix = if ($ti -lt $Terms.Count - 1) { "," } else { "" }
[void]$lines.Add(" }$suffix")
}
[void]$lines.Add(" ].")
$outPath = Join-Path $outDir "$module.erl"
[IO.File]::WriteAllText($outPath, ($lines -join "`r`n"))
}
$count = 0
Get-ChildItem $sourceDir -Filter '*.table' | Sort-Object Name | ForEach-Object {
$terms = Read-TableTerms $_.FullName
if ($terms.Count -gt 0) {
Write-Module $_.BaseName $terms
$count++
}
}
"generated=$count"
@@ -0,0 +1,86 @@
$ErrorActionPreference = 'Stop'
$includeRoots = @(
'apps/game_server/include',
'apps/game_server/include/auto_gen/cache',
'apps/game_server/include/auto_gen/tbllog',
'apps/game_server/include/auto_gen/proto',
'apps/base/include',
'apps/game_config/include',
'apps/game_proto/include'
)
function Get-TopRecordForms([string]$text) {
$forms = New-Object System.Collections.Generic.List[object]
$len = $text.Length
$i = 0
while ($i -lt $len) {
if (($i -eq 0 -or $text[$i - 1] -eq "`n") -and $text[$i] -eq '-' -and $text.Substring($i) -match '^-record\(\s*(''[^'']+''|[A-Za-z0-9_]+)\s*,') {
$name = $Matches[1].Trim("'")
$depth = 0; $inString = $false; $inAtom = $false; $escape = $false; $j = $i
while ($j -lt $len) {
$ch = $text[$j]
if ($inString) {
if ($escape) { $escape = $false } elseif ($ch -eq '\') { $escape = $true } elseif ($ch -eq '"') { $inString = $false }
} elseif ($inAtom) {
if ($escape) { $escape = $false } elseif ($ch -eq '\') { $escape = $true } elseif ($ch -eq "'") { $inAtom = $false }
} else {
if ($ch -eq '"') { $inString = $true }
elseif ($ch -eq "'") { $inAtom = $true }
elseif ($ch -eq '(' -or $ch -eq '[' -or $ch -eq '{') { $depth++ }
elseif ($ch -eq ')' -or $ch -eq ']' -or $ch -eq '}') { if ($depth -gt 0) { $depth-- } }
elseif ($ch -eq '.' -and $depth -eq 0) {
$end = $j + 1
$forms.Add([pscustomobject]@{ Name = $name; Start = $i; End = $end; Text = $text.Substring($i, $end - $i) })
$i = $end - 1
break
}
}
$j++
}
}
$i++
}
return $forms
}
function Remove-DuplicateFieldLines([string]$form) {
$seen = New-Object System.Collections.Generic.HashSet[string]
$out = New-Object System.Collections.Generic.List[string]
$removed = 0
$lines = $form -split "\r?\n"
foreach ($line in $lines) {
$match = [regex]::Match($line, '^\s*,?\s*([a-z][A-Za-z0-9_]*)\b')
if ($match.Success) {
$field = $match.Groups[1].Value
if ($seen.Contains($field)) {
$removed++
continue
}
[void]$seen.Add($field)
}
$out.Add($line)
}
return [pscustomobject]@{ Text = ($out -join "`r`n"); Removed = $removed }
}
$changed = 0
$removedTotal = 0
Get-ChildItem $includeRoots -Recurse -Filter *.hrl | ForEach-Object {
$full = $_.FullName
$text = [IO.File]::ReadAllText($full)
$new = $text
foreach ($record in @(Get-TopRecordForms $text | Sort-Object Start -Descending)) {
$deduped = Remove-DuplicateFieldLines $record.Text
if ($deduped.Removed -gt 0) {
$new = $new.Remove($record.Start, $record.End - $record.Start).Insert($record.Start, $deduped.Text)
$removedTotal += $deduped.Removed
}
}
if ($new -ne $text) {
[IO.File]::WriteAllText($full, $new, [Text.UTF8Encoding]::new($false))
$changed++
}
}
"changed_headers=$changed removed_duplicate_fields=$removedTotal"
@@ -0,0 +1,148 @@
$ErrorActionPreference = 'Stop'
$includeRoots = @(
'apps/game_server/include',
'apps/game_server/include/auto_gen/cache',
'apps/game_server/include/auto_gen/tbllog',
'apps/game_server/include/auto_gen/proto',
'apps/base/include',
'apps/game_config/include',
'apps/game_proto/include'
)
$sourceRoots = @(
'apps/game_server/src',
'apps/game_server/db_patch',
'apps/game_server/db_merge',
'apps/game_server/db_clear'
) | Where-Object { Test-Path $_ }
function Get-TopRecordForms([string]$text) {
$forms = New-Object System.Collections.Generic.List[object]
$len = $text.Length
$i = 0
while ($i -lt $len) {
if (($i -eq 0 -or $text[$i - 1] -eq "`n") -and $text[$i] -eq '-' -and $text.Substring($i) -match '^-record\(\s*(''[^'']+''|[A-Za-z0-9_]+)\s*,') {
$name = $Matches[1].Trim("'")
$depth = 0; $inString = $false; $inAtom = $false; $escape = $false; $j = $i
while ($j -lt $len) {
$ch = $text[$j]
if ($inString) {
if ($escape) { $escape = $false } elseif ($ch -eq '\') { $escape = $true } elseif ($ch -eq '"') { $inString = $false }
} elseif ($inAtom) {
if ($escape) { $escape = $false } elseif ($ch -eq '\') { $escape = $true } elseif ($ch -eq "'") { $inAtom = $false }
} else {
if ($ch -eq '"') { $inString = $true }
elseif ($ch -eq "'") { $inAtom = $true }
elseif ($ch -eq '(' -or $ch -eq '[' -or $ch -eq '{') { $depth++ }
elseif ($ch -eq ')' -or $ch -eq ']' -or $ch -eq '}') { if ($depth -gt 0) { $depth-- } }
elseif ($ch -eq '.' -and $depth -eq 0) {
$end = $j + 1
$form = $text.Substring($i, $end - $i)
$forms.Add([pscustomobject]@{ Name = $name; Start = $i; End = $end; Text = $form })
$i = $end - 1
break
}
}
$j++
}
}
$i++
}
return $forms
}
function Header-Score([string]$name, [string]$path) {
$file = [IO.Path]::GetFileNameWithoutExtension($path)
$p = $path -replace '\\', '/'
$score = 1000
if ($file -eq $name) { $score -= 700 }
if ($file -eq ($name + '_c')) { $score -= 650 }
if ($name -like 'cfg_misc*' -and $file -eq 'cfg_misc') { $score -= 800 }
if ($name -like 'cfg_*' -and $file -eq $name) { $score -= 800 }
if ($p -like 'apps/game_config/include/*') { $score -= 80 }
if ($p -like 'apps/game_server/include/auto_gen/cache/*') { $score -= 70 }
if ($p -like 'apps/game_server/include/*') { $score -= 50 }
return $score
}
function Get-RecordFields([string]$form) {
$fields = New-Object System.Collections.Generic.HashSet[string]
$bodyStart = $form.IndexOf('{')
$bodyEnd = $form.LastIndexOf('}')
if ($bodyStart -lt 0 -or $bodyEnd -le $bodyStart) { return $fields }
$body = $form.Substring($bodyStart + 1, $bodyEnd - $bodyStart - 1)
foreach ($m in [regex]::Matches($body, '(?m)(?:^|,)\s*([a-z][A-Za-z0-9_]*)\s*(?:=|::|,|$)')) {
[void]$fields.Add($m.Groups[1].Value)
}
return $fields
}
$usage = @{}
Get-ChildItem $sourceRoots -Recurse -Filter *.erl | ForEach-Object {
$text = [IO.File]::ReadAllText($_.FullName)
foreach ($m in [regex]::Matches($text, '#([a-zA-Z0-9_]+)\s*\{([^}]*)\}', 'Singleline')) {
$name = $m.Groups[1].Value
if (-not $usage.ContainsKey($name)) {
$usage[$name] = New-Object System.Collections.Generic.HashSet[string]
}
foreach ($fm in [regex]::Matches($m.Groups[2].Value, '\b([a-z][A-Za-z0-9_]*)\s*=')) {
[void]$usage[$name].Add($fm.Groups[1].Value)
}
}
}
$records = @{}
Get-ChildItem $includeRoots -Recurse -Filter *.hrl | ForEach-Object {
$rel = $_.FullName.Substring((Get-Location).Path.Length + 1)
$text = [IO.File]::ReadAllText($_.FullName)
foreach ($record in Get-TopRecordForms $text) {
if (-not $records.ContainsKey($record.Name)) {
$records[$record.Name] = New-Object System.Collections.Generic.List[object]
}
$fieldSet = Get-RecordFields $record.Text
if ($null -eq $fieldSet) {
$fieldSet = New-Object System.Collections.Generic.HashSet[string]
}
$records[$record.Name].Add([pscustomobject]@{
Path = $rel
Score = Header-Score $record.Name $rel
Fields = $fieldSet
})
}
}
$updatesByPath = @{}
foreach ($name in $usage.Keys) {
if (-not $records.ContainsKey($name)) { continue }
$best = @($records[$name] | Sort-Object Score, Path | Select-Object -First 1)[0]
$missing = @($usage[$name] | Where-Object { -not $best.Fields.Contains($_) } | Sort-Object)
if ($missing.Count -eq 0) { continue }
if (-not $updatesByPath.ContainsKey($best.Path)) {
$updatesByPath[$best.Path] = @{}
}
$updatesByPath[$best.Path][$name] = $missing
}
$changedHeaders = 0
$addedFields = 0
foreach ($path in $updatesByPath.Keys) {
$full = Join-Path (Get-Location) $path
$text = [IO.File]::ReadAllText($full)
$forms = @(Get-TopRecordForms $text | Sort-Object Start -Descending)
foreach ($form in $forms) {
if (-not $updatesByPath[$path].ContainsKey($form.Name)) { continue }
$missing = $updatesByPath[$path][$form.Name]
$insertAt = $form.Start + $form.Text.LastIndexOf('}')
$addition = ''
foreach ($field in $missing) {
$addition += ",`r`n $field = undefined"
$addedFields++
}
$text = $text.Insert($insertAt, $addition)
}
[IO.File]::WriteAllText($full, $text, [Text.UTF8Encoding]::new($false))
$changedHeaders++
}
"changed_headers=$changedHeaders added_fields=$addedFields"
@@ -0,0 +1,72 @@
$ErrorActionPreference = 'Stop'
$sourceDir = 'apps/game_server/cache_recovered'
$prefixDir = 'apps/game_server/cache'
$outDir = 'apps/game_server/cache_recovered_grouped'
if (-not (Test-Path $sourceDir)) {
throw "Missing source dir: $sourceDir"
}
if (Test-Path $outDir) {
$resolved = (Resolve-Path $outDir).Path
$root = (Resolve-Path '.').Path
if (-not $resolved.StartsWith($root, [StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to clean outside workspace: $resolved"
}
Remove-Item -LiteralPath $resolved -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
$knownPrefixes = @()
if (Test-Path $prefixDir) {
$knownPrefixes = Get-ChildItem $prefixDir -Filter '*.table' |
ForEach-Object { $_.BaseName } |
Sort-Object Length -Descending
}
function Get-GroupName([string]$TableName, $KnownPrefixes) {
foreach ($prefix in $KnownPrefixes) {
if ($TableName -eq $prefix -or $TableName.StartsWith("$prefix`_", [StringComparison]::Ordinal)) {
return $prefix
}
}
$idx = $TableName.IndexOf('_')
if ($idx -gt 0) {
return $TableName.Substring(0, $idx)
}
return $TableName
}
$groups = @{}
Get-ChildItem $sourceDir -Filter '*.table' | Sort-Object Name | ForEach-Object {
$tableName = $_.BaseName
$groupName = Get-GroupName $tableName $knownPrefixes
if (-not $groups.ContainsKey($groupName)) {
$groups[$groupName] = [System.Collections.Generic.List[object]]::new()
}
[void]$groups[$groupName].Add($_)
}
foreach ($groupName in ($groups.Keys | Sort-Object)) {
$lines = [System.Collections.Generic.List[string]]::new()
[void]$lines.Add("%% recovered grouped table file: $groupName.table")
[void]$lines.Add("%% source: apps/game_server/cache_recovered/*.table")
[void]$lines.Add('')
foreach ($file in ($groups[$groupName] | Sort-Object Name)) {
$content = [IO.File]::ReadAllText($file.FullName).Trim()
$content = [regex]::Replace($content, '(?m)^%% recovered from .*\r?\n', '')
[void]$lines.Add("%% -----------------------------------------------------------------------------")
[void]$lines.Add("%% $($file.BaseName)")
[void]$lines.Add($content)
[void]$lines.Add('')
}
$outFile = Join-Path $outDir "$groupName.table"
[IO.File]::WriteAllText($outFile, ($lines -join "`r`n"))
}
"source_files=$((Get-ChildItem $sourceDir -Filter '*.table').Count)"
"grouped_files=$((Get-ChildItem $outDir -Filter '*.table').Count)"
@@ -0,0 +1,138 @@
$ErrorActionPreference = 'Stop'
$roots = @(
'apps/base/src',
'apps/game_config/src',
'apps/game_proto/src',
'apps/game_server/src',
'apps/game_server/db_patch',
'apps/game_server/db_merge',
'apps/game_server/db_clear',
'db_patch',
'db_merge',
'db_clear'
) | Where-Object { Test-Path $_ }
$changed = 0
$recordFiles = 0
function Get-AppRoot($path) {
$full = (Resolve-Path $path).Path
$apps = (Resolve-Path 'apps').Path
if ($full.StartsWith($apps, [StringComparison]::OrdinalIgnoreCase)) {
$rest = $full.Substring($apps.Length).TrimStart('\', '/')
$app = $rest -split '[\\/]'
$app = $app[0]
return Join-Path $apps $app
}
return (Resolve-Path '.').Path
}
function Get-ModuleName($lines) {
foreach ($line in $lines) {
if ($line -match '^\s*-module\(([^)]+)\)\.') {
return $Matches[1].Trim()
}
}
return $null
}
function Insert-Include($lines, $includeLine) {
$lastInclude = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^\s*-include(_lib)?\(') {
$lastInclude = $i
}
}
if ($lastInclude -ge 0) {
$before = @()
if ($lastInclude -ge 0) { $before = $lines[0..$lastInclude] }
$after = @()
if ($lastInclude + 1 -lt $lines.Count) { $after = $lines[($lastInclude + 1)..($lines.Count - 1)] }
return @($before + $includeLine + $after)
}
$moduleLine = -1
for ($i = 0; $i -lt $lines.Count; $i++) {
if ($lines[$i] -match '^\s*-module\(') {
$moduleLine = $i
break
}
}
if ($moduleLine -ge 0) {
$before = $lines[0..$moduleLine]
$after = @()
if ($moduleLine + 1 -lt $lines.Count) { $after = $lines[($moduleLine + 1)..($lines.Count - 1)] }
return @($before + '' + $includeLine + $after)
}
return @($includeLine + $lines)
}
Get-ChildItem $roots -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
$lines = [System.Collections.Generic.List[string]]::new()
$text -split "`r?`n", -1 | ForEach-Object { [void]$lines.Add($_) }
$module = Get-ModuleName $lines
if (-not $module) { return }
$newLines = [System.Collections.Generic.List[string]]::new()
$records = [System.Collections.Generic.List[string]]::new()
$i = 0
while ($i -lt $lines.Count) {
$line = $lines[$i]
if ($line -match '^\s*-record\(') {
$block = [System.Collections.Generic.List[string]]::new()
while ($i -lt $lines.Count) {
[void]$block.Add($lines[$i])
if ($lines[$i] -match '\}\)\.') {
break
}
$i++
}
[void]$records.Add(($block -join "`r`n"))
$i++
continue
}
[void]$newLines.Add($line)
$i++
}
if ($records.Count -eq 0) { return }
$appRoot = Get-AppRoot $path
$includeDir = Join-Path $appRoot 'include\auto_records'
New-Item -ItemType Directory -Force -Path $includeDir | Out-Null
$safeModule = $module -replace '[^A-Za-z0-9_]', '_'
$hrlName = "$safeModule`_records.hrl"
$hrlPath = Join-Path $includeDir $hrlName
$guard = "__AUTO_RECORDS_$($safeModule.ToUpperInvariant())_HRL__"
$hrl = @(
"-ifndef($guard).",
"-define($guard, true).",
''
) + $records + @(
'',
'-endif.'
)
if (Test-Path $hrlPath) {
$hrlItem = Get-Item $hrlPath
if ($hrlItem.IsReadOnly) { $hrlItem.IsReadOnly = $false }
}
[IO.File]::WriteAllText($hrlPath, ($hrl -join "`r`n"))
$include = "-include(""auto_records/$hrlName"")."
if (($newLines -join "`n") -notmatch [regex]::Escape($include)) {
$newLines = [System.Collections.Generic.List[string]](Insert-Include $newLines $include)
}
if ($_.IsReadOnly) { $_.IsReadOnly = $false }
[IO.File]::WriteAllText($path, ($newLines -join "`r`n"))
$changed++
$recordFiles += $records.Count
}
"changed_files=$changed"
"moved_records=$recordFiles"
@@ -0,0 +1,261 @@
$ErrorActionPreference = 'Stop'
$srcDir = 'apps/game_server/src/auto_gen/cache'
$outDir = 'apps/game_server/cache_recovered'
if (Test-Path $outDir) {
$resolved = (Resolve-Path $outDir).Path
$root = (Resolve-Path '.').Path
if (-not $resolved.StartsWith($root, [StringComparison]::OrdinalIgnoreCase)) {
throw "Refusing to clean outside workspace: $resolved"
}
Remove-Item -LiteralPath $resolved -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
function Read-FunctionExpr([string]$Text, [string]$Name) {
$m = [regex]::Match($Text, "(?ms)^$([regex]::Escape($Name))\(\)\s*->\s*(?<expr>.*?)(?<!\\)\.")
if ($m.Success) { return $m.Groups['expr'].Value.Trim() }
return $null
}
function Parse-AtomList([string]$Expr) {
if ([string]::IsNullOrWhiteSpace($Expr)) { return @() }
$m = [regex]::Match($Expr, '(?s)\[(?<body>.*)\]')
if (-not $m.Success) { return @() }
$body = $m.Groups['body'].Value -replace '\s+', ''
if ($body.Length -eq 0) { return @() }
return @($body.Split(',') | ForEach-Object { $_.Trim("' ") } | Where-Object { $_ -ne '' })
}
function Parse-Atom([string]$Expr) {
if ($null -eq $Expr) { return $null }
return (($Expr -replace '\s+', '')).Trim("' ")
}
function Parse-Bool([string]$Expr, [bool]$Default) {
if ($null -eq $Expr) { return $Default }
$v = ($Expr -replace '\s+', '').Trim()
if ($v -eq 'true') { return $true }
if ($v -eq 'false') { return $false }
return $Default
}
function Decode-ErlString([string]$S) {
$S = [regex]::Replace($S, '\\x\{([0-9A-Fa-f]+)\}', {
param($m)
[string][char]([Convert]::ToInt32($m.Groups[1].Value, 16))
})
$S = $S -replace '\\n', "`n"
$S = $S -replace '\\t', "`t"
$S = $S -replace '\\r', "`r"
$S = $S -replace '\\"', '"'
$S = $S -replace '\\\\', '\'
return $S
}
function Extract-ErlStrings([string]$Block) {
$parts = [System.Collections.Generic.List[string]]::new()
foreach ($m in [regex]::Matches($Block, '"((?:\\.|[^"\\])*)"')) {
[void]$parts.Add((Decode-ErlString $m.Groups[1].Value))
}
return ($parts -join '')
}
function Get-CreateSql([string]$Text) {
$m = [regex]::Match($Text, '(?ms)create\([^)]*\)\s*->.*?Sql\s*=\s*<<(?<bin>.*?)\/utf8>>')
if (-not $m.Success) { return '' }
return Extract-ErlStrings $m.Groups['bin'].Value
}
function Get-DbColumns([string]$Sql) {
$cols = @{}
if ([string]::IsNullOrWhiteSpace($Sql)) { return $cols }
foreach ($m in [regex]::Matches($Sql, '`(?<name>[^`]+)`\s+(?<dbtype>bigint|int|tinyint|varchar|text|blob|mediumblob)(?:\((?<len>\d+)\))?', 'IgnoreCase')) {
$name = $m.Groups['name'].Value
if ($name -in @('PRIMARY', 'KEY')) { continue }
$dbType = $m.Groups['dbtype'].Value.ToLowerInvariant()
$len = if ($m.Groups['len'].Success) { [int64]$m.Groups['len'].Value } else { 0 }
$cols[$name] = @{ db_type = $dbType; len = $len }
}
return $cols
}
function Get-FieldDefault([string]$Text, [string]$Field) {
$m = [regex]::Match($Text, "maps:get\($([regex]::Escape($Field)),\s*RecOrMap,\s*(?<def>[^)]+)\)")
if ($m.Success) { return $m.Groups['def'].Value.Trim() }
return $null
}
function Infer-FieldMeta([string]$Text, [string]$Table, [string]$Field, $DbCols) {
$type = $null
$len = $null
$typeMatch = [regex]::Match($Text, "\[$([regex]::Escape($Table))\s+$([regex]::Escape($Field))\]\s+type_not_(?<type>[a-z_]+)")
if ($typeMatch.Success) {
switch ($typeMatch.Groups['type'].Value) {
'integer' { $type = 'int' }
'binary' { $type = 'binary' }
'list' { $type = 'list' }
'map' { $type = 'map' }
'tuple' { $type = 'tuple' }
'atom' { $type = 'atom' }
'boolean' { $type = 'boolean' }
default { $type = 'any' }
}
}
$fieldPos = if ($typeMatch.Success) { $typeMatch.Index } else { $Text.IndexOf("[$Table $Field]") }
if ($fieldPos -ge 0) {
$nextType = [regex]::Match($Text.Substring($fieldPos + 1), "\[$([regex]::Escape($Table))\s+[^]]+\]\s+type_not_[a-z_]+")
$sliceLen =
if ($nextType.Success) {
$nextType.Index + 1
} else {
[Math]::Min(900, $Text.Length - $fieldPos)
}
$slice = $Text.Substring($fieldPos, $sliceLen) -replace '"\s*"', ''
$lenMatch = [regex]::Match($slice, 'value_len_invalid\((?<len>\d+)')
if ($lenMatch.Success) {
$len = [int64]$lenMatch.Groups['len'].Value
} else {
$maxMatch = [regex]::Match($slice, 'value_invalid\((?<max>[0-9]+|true\|false)')
if ($maxMatch.Success) {
switch ($maxMatch.Groups['max'].Value) {
'9223372036854775807' { $len = 20 }
'2147483647' { $len = 11 }
'true|false' { $len = 8 }
default { $len = 20 }
}
}
}
}
if ($DbCols.ContainsKey($Field)) {
$db = $DbCols[$Field]
if ($null -eq $type) {
switch ($db.db_type) {
'bigint' { $type = 'int' }
'int' { $type = 'int' }
'tinyint' { $type = 'boolean' }
'varchar' { $type = 'binary' }
'blob' { $type = 'any' }
'mediumblob' { $type = 'any' }
default { $type = 'list' }
}
}
if ($null -eq $len) { $len = $db.len }
}
$def = Get-FieldDefault $Text $Field
if ($null -eq $type) {
if ($def -eq '<<>>') { $type = 'binary' }
elseif ($def -eq '[]') { $type = 'list' }
elseif ($def -eq '#{}') { $type = 'map' }
elseif ($def -match '^\{') { $type = 'tuple' }
elseif ($def -eq 'true' -or $def -eq 'false') { $type = 'boolean' }
elseif ($def -match '^-?\d+$') { $type = 'int' }
else { $type = 'any' }
}
if ($null -eq $len) {
switch ($type) {
'int' { $len = 20 }
'boolean' { $len = 8 }
'binary' { $len = 0 }
default { $len = 0 }
}
}
if ($null -eq $def -or $def -eq '') {
switch ($type) {
'int' { $def = '0' }
'boolean' { $def = 'false' }
'binary' { $def = '<<>>' }
'list' { $def = '[]' }
'map' { $def = '#{}' }
'tuple' { $def = '{}' }
default { $def = 'undefined' }
}
}
return @{ type = $type; len = $len; default = $def; db_save = $DbCols.ContainsKey($Field) }
}
function Format-AtomList($List) {
if ($List.Count -eq 0) { return '[]' }
return '[' + (($List | ForEach-Object { $_ }) -join ', ') + ']'
}
function Add-Option([System.Collections.Generic.List[string]]$Parts, [string]$Key, [string]$Value) {
[void]$Parts.Add("$Key=>$Value")
}
$files = Get-ChildItem $srcDir -Filter '*_c.erl' | Sort-Object Name
$ok = 0
$failed = [System.Collections.Generic.List[string]]::new()
foreach ($file in $files) {
try {
$text = [IO.File]::ReadAllText($file.FullName)
$table = Parse-Atom (Read-FunctionExpr $text 'record_name')
if ([string]::IsNullOrWhiteSpace($table)) { throw 'record_name/0 not found' }
$fields = @(Parse-AtomList (Read-FunctionExpr $text 'field_list') | Where-Object { $_ -ne '_id' })
$keys = @(Parse-AtomList (Read-FunctionExpr $text 'key_list'))
$indexes = @(Parse-AtomList (Read-FunctionExpr $text 'index_list'))
$preLoad = Parse-Bool (Read-FunctionExpr $text 'is_pre_load') $false
$clearAll = Parse-Bool (Read-FunctionExpr $text 'can_clear_all') $false
$tableType = Parse-Atom (Read-FunctionExpr $text 'table_type')
$dbSaveTime = Parse-Atom (Read-FunctionExpr $text 'db_save_time')
$dbSaveCount = Parse-Atom (Read-FunctionExpr $text 'db_save_count')
$uidField = $null
$uidMatch = [regex]::Match($text, 'SELECT IFNULL\(MAX\(`(?<field>[^`]+)`\), 0\)')
if ($uidMatch.Success) { $uidField = $uidMatch.Groups['field'].Value }
$sql = Get-CreateSql $text
$dbCols = Get-DbColumns $sql
$lines = [System.Collections.Generic.List[string]]::new()
[void]$lines.Add('%% recovered from src/auto_gen/cache/' + $file.Name)
[void]$lines.Add('{' + $table + ',')
[void]$lines.Add(' [')
for ($i = 0; $i -lt $fields.Count; $i++) {
$field = $fields[$i]
$meta = Infer-FieldMeta $text $table $field $dbCols
$comma = if ($i -lt $fields.Count - 1) { ',' } else { '' }
$comment = $field
$line = " {$field, $($meta.type), $($meta.len), `"$comment`", $($meta.db_save.ToString().ToLowerInvariant()), $($meta.default)}$comma"
[void]$lines.Add($line)
}
[void]$lines.Add(' ],')
$optParts = [System.Collections.Generic.List[string]]::new()
if ($tableType -and $tableType -ne 'set') { Add-Option $optParts 'type' $tableType }
if ($indexes.Count -gt 0) { Add-Option $optParts 'indexes' (Format-AtomList $indexes) }
if ($uidField) { Add-Option $optParts 'uid_field' $uidField }
if ($preLoad) { Add-Option $optParts 'pre_load' 'true' }
if ($clearAll) { Add-Option $optParts 'clear_all' 'true' }
if ($dbSaveTime -and $dbSaveTime -ne 'undefined') { Add-Option $optParts 'db_save_time' $dbSaveTime }
if ($dbSaveCount -and $dbSaveCount -ne 'undefined') { Add-Option $optParts 'db_save_count' $dbSaveCount }
$opt = if ($optParts.Count -eq 0) { '#{}' } else { '#{' + ($optParts -join ',') + '}' }
[void]$lines.Add(' ' + (Format-AtomList $keys) + ', ' + $opt)
[void]$lines.Add('}.')
[void]$lines.Add('')
$outBase = $file.BaseName -replace '_c$', ''
$outFile = Join-Path $outDir "$outBase.table"
[IO.File]::WriteAllText($outFile, ($lines -join "`r`n"))
$ok++
} catch {
[void]$failed.Add("$($file.Name): $($_.Exception.Message)")
}
}
"recovered=$ok"
"failed=$($failed.Count)"
if ($failed.Count -gt 0) {
$failed | ForEach-Object { "FAILED $_" }
}
@@ -0,0 +1,34 @@
$ErrorActionPreference = 'Stop'
$root = 'apps/game_server/src/behavior_tree'
$changed = 0
Get-ChildItem $root -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
$pattern = '(?ms)^\s*-type node_id\(\) :: integer\(\)\.\s*' +
'-record\(tree_node,\{.*?' +
'-type bt_operator\(\) :: integer\(\)\.\s*'
$new = [regex]::Replace($text, $pattern, '')
$pattern2 = '(?ms)^\s*-type node_id\(\) :: integer\(\)\.\s*' +
'-record\(tree_node,\{.*?' +
'-type bt_status\(\) :: 1 \| 2 \| 3 \| 4\.\s*'
$new = [regex]::Replace($new, $pattern2, '')
$battlePattern = '(?ms)^\s*(?:-type object_id\(\) :: integer\(\)\.\s*)?' +
'-record\(battle_object,\{.*?' +
'-record\(battle_command_camp_info,\{.*?\}\)\.\s*'
$new = [regex]::Replace($new, $battlePattern, '')
$battleObjectPattern = '(?ms)^\s*(?:-type object_id\(\) :: integer\(\)\.\s*)?' +
'-record\(battle_object,\{.*?' +
'-record\(battle_action_object,\{.*?\}\)\.\s*'
$new = [regex]::Replace($new, $battleObjectPattern, '')
if ($new -ne $text) {
if ($_.IsReadOnly) {
$_.IsReadOnly = $false
}
[IO.File]::WriteAllText($path, $new)
$changed++
}
}
"changed_files=$changed"
@@ -0,0 +1,37 @@
$ErrorActionPreference = 'Stop'
$roots = @(
'apps/game_server/src',
'apps/game_server/db_patch',
'apps/game_server/db_merge',
'apps/game_server/db_clear'
)
$changed = 0
$patterns = @(
'(?ms)^\s*(?:-type object_id\(\) :: integer\(\)\.\s*)?-record\(battle_object,\{.*?-record\(battle_command_camp_info,\{.*?\}\)\.\s*',
'(?ms)^\s*(?:-type object_id\(\) :: integer\(\)\.\s*)?-record\(battle_object,\{.*?-record\(battle_action_object,\{.*?\}\)\.\s*',
'(?ms)^\s*-record\(battle_command_camp_info,\{.*?-record\(battle_processing,\{.*?\}\)\.\s*',
'(?ms)^\s*-record\(battle_command_camp_info,\{.*?\}\)\.\s*'
)
foreach ($root in $roots) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem $root -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
$new = $text
foreach ($pattern in $patterns) {
$new = [regex]::Replace($new, $pattern, '')
}
if ($new -ne $text) {
if ($_.IsReadOnly) {
$_.IsReadOnly = $false
}
[IO.File]::WriteAllText($path, $new)
$changed++
}
}
}
"changed_files=$changed"
@@ -0,0 +1,20 @@
$ErrorActionPreference = 'Stop'
$roots = @('apps/game_server/src', 'apps/game_server/db_patch', 'apps/game_server/db_merge', 'apps/game_server/db_clear')
$changed = 0
foreach ($root in $roots) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem $root -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
$new = [regex]::Replace($text, '^\s*-type big_battle_record_type\(\) :: .*?\.\s*$', '', 'Multiline')
$new = [regex]::Replace($new, '^\s*-type big_battle_grab_type\(\) :: .*?\.\s*$', '', 'Multiline')
if ($new -ne $text) {
if ($_.IsReadOnly) { $_.IsReadOnly = $false }
[IO.File]::WriteAllText($path, $new)
$changed++
}
}
}
"changed_files=$changed"
@@ -0,0 +1,19 @@
$ErrorActionPreference = 'Stop'
$roots = @('apps/game_server/src', 'apps/game_server/db_patch', 'apps/game_server/db_merge', 'apps/game_server/db_clear')
$changed = 0
foreach ($root in $roots) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem $root -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
$new = [regex]::Replace($text, '^\s*-type farm_log_arg_item\(\) :: .*?\.\s*$', '', 'Multiline')
if ($new -ne $text) {
if ($_.IsReadOnly) { $_.IsReadOnly = $false }
[IO.File]::WriteAllText($path, $new)
$changed++
}
}
}
"changed_files=$changed"
@@ -0,0 +1,20 @@
$ErrorActionPreference = 'Stop'
$roots = @('apps/game_server/src', 'apps/game_server/db_patch', 'apps/game_server/db_merge', 'apps/game_server/db_clear')
$changed = 0
foreach ($root in $roots) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem $root -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
$new = [regex]::Replace($text, '^\s*-type privilege_id\(\) :: .*?\.\s*$', '', 'Multiline')
$new = [regex]::Replace($new, '^\s*-type privilege_source\(\) :: .*?\.\s*$', '', 'Multiline')
if ($new -ne $text) {
if ($_.IsReadOnly) { $_.IsReadOnly = $false }
[IO.File]::WriteAllText($path, $new)
$changed++
}
}
}
"changed_files=$changed"
@@ -0,0 +1,31 @@
$ErrorActionPreference = 'Stop'
$roots = @('apps/game_server/src', 'apps/game_server/db_patch', 'apps/game_server/db_merge', 'apps/game_server/db_clear')
$patterns = @(
'^\s*-type tutor_type\(\) :: .*?\.\s*$',
'^\s*-type tutor_op\(\) :: .*?\.\s*$',
'^\s*-type tutor_sub_type\(\) :: .*?\.\s*$',
'^\s*-type tutor_dismiss_type\(\) :: .*?\.\s*$',
'^\s*-type tutor_relation_net_privilege_state\(\) :: .*?\.\s*$',
'^\s*-type tutor_guide_status\(\) :: .*?\.\s*$'
)
$changed = 0
foreach ($root in $roots) {
if (-not (Test-Path $root)) { continue }
Get-ChildItem $root -Recurse -Filter *.erl | ForEach-Object {
$path = $_.FullName
$text = [IO.File]::ReadAllText($path)
$new = $text
foreach ($pattern in $patterns) {
$new = [regex]::Replace($new, $pattern, '', 'Multiline')
}
if ($new -ne $text) {
if ($_.IsReadOnly) { $_.IsReadOnly = $false }
[IO.File]::WriteAllText($path, $new)
$changed++
}
}
}
"changed_files=$changed"
@@ -0,0 +1,58 @@
$ErrorActionPreference = 'Stop'
$files = Get-ChildItem apps -Recurse -Filter *.erl
$changed = 0
foreach ($file in $files) {
$text = [IO.File]::ReadAllText($file.FullName)
if ($text -notmatch '-include\("auto_records/') { continue }
$lines = [System.Collections.Generic.List[string]]::new()
$text -split "`r?`n", -1 | ForEach-Object { [void]$lines.Add($_) }
$includes = [System.Collections.Generic.List[string]]::new()
$kept = [System.Collections.Generic.List[string]]::new()
foreach ($line in $lines) {
if ($line -match '^\s*-include\("auto_records/[^"]+"\)\.') {
if (-not $includes.Contains($line.Trim())) {
[void]$includes.Add($line.Trim())
}
} else {
[void]$kept.Add($line)
}
}
if ($includes.Count -eq 0) { continue }
$insertAt = -1
for ($i = 0; $i -lt $kept.Count; $i++) {
$line = $kept[$i]
if ($line -match '^\s*-(type|spec)\b' -or
$line -match '^\s*[a-z][A-Za-z0-9_@]*\s*\(') {
$insertAt = $i
break
}
}
if ($insertAt -lt 0) {
for ($i = 0; $i -lt $kept.Count; $i++) {
if ($kept[$i] -match '^\s*-module\(') {
$insertAt = $i + 1
break
}
}
}
if ($insertAt -lt 0) { $insertAt = 0 }
$newLines = @()
if ($insertAt -gt 0) { $newLines += $kept[0..($insertAt - 1)] }
$newLines += $includes
if ($insertAt -lt $kept.Count) { $newLines += $kept[$insertAt..($kept.Count - 1)] }
$newText = $newLines -join "`r`n"
if ($newText -ne $text) {
if ($file.IsReadOnly) { $file.IsReadOnly = $false }
[IO.File]::WriteAllText($file.FullName, $newText)
$changed++
}
}
"changed_files=$changed"
@@ -0,0 +1,234 @@
$ErrorActionPreference = 'Stop'
$includeRoots = @(
'apps/game_server/include',
'apps/game_server/include/auto_gen/cache',
'apps/game_server/include/auto_gen/tbllog',
'apps/game_server/include/auto_gen/proto',
'apps/base/include',
'apps/game_config/include',
'apps/game_proto/include'
)
$sourceRoots = @(
'apps/game_server/src',
'apps/game_server/db_patch',
'apps/game_server/db_merge',
'apps/game_server/db_clear'
) | Where-Object { Test-Path $_ }
function Get-TopRecordForms([string]$text) {
$forms = New-Object System.Collections.Generic.List[object]
$len = $text.Length
$i = 0
while ($i -lt $len) {
if (($i -eq 0 -or $text[$i - 1] -eq "`n") -and $text[$i] -eq '-' -and $text.Substring($i) -match '^-record\(\s*(''[^'']+''|[A-Za-z0-9_]+)\s*,') {
$name = $Matches[1].Trim("'")
$depth = 0
$inString = $false
$inAtom = $false
$escape = $false
$j = $i
while ($j -lt $len) {
$ch = $text[$j]
if ($inString) {
if ($escape) { $escape = $false }
elseif ($ch -eq '\') { $escape = $true }
elseif ($ch -eq '"') { $inString = $false }
} elseif ($inAtom) {
if ($escape) { $escape = $false }
elseif ($ch -eq '\') { $escape = $true }
elseif ($ch -eq "'") { $inAtom = $false }
} else {
if ($ch -eq '"') { $inString = $true }
elseif ($ch -eq "'") { $inAtom = $true }
elseif ($ch -eq '(' -or $ch -eq '[' -or $ch -eq '{') { $depth++ }
elseif ($ch -eq ')' -or $ch -eq ']' -or $ch -eq '}') { if ($depth -gt 0) { $depth-- } }
elseif ($ch -eq '.' -and $depth -eq 0) {
$end = $j + 1
while ($end -lt $len -and ($text[$end] -eq "`r" -or $text[$end] -eq "`n")) { $end++ }
$form = $text.Substring($i, $end - $i)
$forms.Add([pscustomobject]@{ Name = $name; Start = $i; End = $end; Text = $form })
$i = $end - 1
break
}
}
$j++
}
}
$i++
}
return $forms
}
function Normalize-Record([string]$text) {
return ([regex]::Replace($text, '\s+', '')).Trim()
}
function Header-Score([string]$name, [string]$path) {
$file = [IO.Path]::GetFileNameWithoutExtension($path)
$p = $path -replace '\\', '/'
$score = 1000
if ($file -eq $name) { $score -= 700 }
if ($file -eq ($name + '_c')) { $score -= 650 }
if ($name -like 'cfg_misc*' -and $file -eq 'cfg_misc') { $score -= 800 }
if ($name -like 'cfg_*' -and $file -eq $name) { $score -= 800 }
if ($name -like 'p_*' -and $file -eq 'proto_type_pb') { $score -= 500 }
if ($p -like 'apps/game_config/include/*') { $score -= 80 }
if ($p -like 'apps/game_server/include/auto_gen/cache/*') { $score -= 70 }
if ($p -like 'apps/game_server/include/auto_gen/proto/*') { $score -= 60 }
if ($p -like 'apps/game_server/include/*') { $score -= 50 }
return $score
}
function New-HeaderPath([string]$name) {
if ($name -like 'cfg_misc*') { return 'apps/game_config/include/cfg_misc.hrl' }
if ($name -like 'cfg_*') { return "apps/game_config/include/$name.hrl" }
if ($name -like 'p_*') { return 'apps/game_server/include/auto_gen/proto/proto_type_pb.hrl' }
return $null
}
function Ensure-HeaderRecord([string]$path, [string]$name, [string]$form) {
$full = Join-Path (Get-Location) $path
if (-not (Test-Path $full)) {
$guard = '__' + (($name.ToUpperInvariant()) -replace '[^A-Z0-9_]', '_') + '_HRL__'
$content = @(
"-ifndef($guard).",
"-define($guard, true).",
"",
$form.Trim(),
"",
"-endif.",
""
) -join "`r`n"
[IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($full)) | Out-Null
[IO.File]::WriteAllText($full, $content, [Text.UTF8Encoding]::new($false))
return $true
}
$text = [IO.File]::ReadAllText($full)
$records = @(Get-TopRecordForms $text | Where-Object { $_.Name -eq $name })
if ($records.Count -gt 0) {
$record = $records[0]
if ((Normalize-Record $record.Text) -eq (Normalize-Record $form)) {
return $false
}
$new = $text.Remove($record.Start, $record.End - $record.Start).Insert($record.Start, $form.Trim() + "`r`n`r`n")
[IO.File]::WriteAllText($full, $new, [Text.UTF8Encoding]::new($false))
return $true
}
$insert = $text.Length
$endifs = [regex]::Matches($text, '(?m)^-endif\.\s*$')
if ($endifs.Count -gt 0 -and $text -match '(?m)^-ifndef\(') {
$insert = $endifs[$endifs.Count - 1].Index
}
$addition = $form.Trim() + "`r`n`r`n"
$newText = $text.Insert($insert, $addition)
[IO.File]::WriteAllText($full, $newText, [Text.UTF8Encoding]::new($false))
return $true
}
$headerIndex = @{}
Get-ChildItem $includeRoots -Recurse -Filter *.hrl | ForEach-Object {
$rel = $_.FullName.Substring((Get-Location).Path.Length + 1)
$text = [IO.File]::ReadAllText($_.FullName)
foreach ($record in Get-TopRecordForms $text) {
if (-not $headerIndex.ContainsKey($record.Name)) {
$headerIndex[$record.Name] = New-Object System.Collections.Generic.List[object]
}
$headerIndex[$record.Name].Add([pscustomobject]@{
Path = $rel
Include = [IO.Path]::GetFileName($rel)
Score = Header-Score $record.Name $rel
})
}
}
$sourceRecords = @{}
Get-ChildItem $sourceRoots -Recurse -Filter *.erl | ForEach-Object {
$text = [IO.File]::ReadAllText($_.FullName)
foreach ($record in Get-TopRecordForms $text) {
if (-not $sourceRecords.ContainsKey($record.Name)) {
$sourceRecords[$record.Name] = New-Object System.Collections.Generic.List[string]
}
$sourceRecords[$record.Name].Add($record.Text)
}
}
$recordTarget = @{}
$headerChanged = 0
$skippedNames = New-Object System.Collections.Generic.List[string]
foreach ($name in $sourceRecords.Keys) {
$forms = @($sourceRecords[$name] | ForEach-Object { [pscustomobject]@{ Raw = $_; Norm = Normalize-Record $_ } })
$uniqueNorms = @($forms | Select-Object -ExpandProperty Norm -Unique)
if ($uniqueNorms.Count -ne 1) {
if ($skippedNames.Count -lt 80) { $skippedNames.Add("$name(multiple)") }
continue
}
$form = ($forms | Select-Object -First 1).Raw
$target = $null
if ($headerIndex.ContainsKey($name)) {
$target = @($headerIndex[$name] | Sort-Object Score, Include | Select-Object -First 1)[0].Path
} else {
$target = New-HeaderPath $name
}
if (-not $target) {
if ($skippedNames.Count -lt 80) { $skippedNames.Add($name) }
continue
}
if (Ensure-HeaderRecord $target $name $form) { $headerChanged++ }
$recordTarget[$name] = [pscustomobject]@{ Path = $target; Include = [IO.Path]::GetFileName($target) }
}
$changedFiles = 0
$removedRecords = 0
Get-ChildItem $sourceRoots -Recurse -Filter *.erl | ForEach-Object {
$full = $_.FullName
$text = [IO.File]::ReadAllText($full)
$records = @(Get-TopRecordForms $text | Where-Object { $recordTarget.ContainsKey($_.Name) })
if ($records.Count -eq 0) { return }
$includes = New-Object System.Collections.Generic.HashSet[string]
foreach ($record in $records) {
[void]$includes.Add($recordTarget[$record.Name].Include)
}
$new = $text
foreach ($record in @($records | Sort-Object Start -Descending)) {
$new = $new.Remove($record.Start, $record.End - $record.Start)
}
$existing = [regex]::Matches($new, '^-include(?:_lib)?\("([^"]+)"\)\.', 'Multiline') |
ForEach-Object { [IO.Path]::GetFileName($_.Groups[1].Value) }
foreach ($include in $existing) {
[void]$includes.Remove($include)
}
if ($includes.Count -gt 0) {
$includeLines = (($includes | Sort-Object | ForEach-Object { '-include("' + $_ + '").' }) -join "`r`n") + "`r`n"
$lastInclude = $null
$includeMatches = [regex]::Matches($new, '^-include(?:_lib)?\("[^"]+"\)\.[\r\n]+', 'Multiline')
if ($includeMatches.Count -gt 0) {
$lastInclude = $includeMatches[$includeMatches.Count - 1]
$new = $new.Insert($lastInclude.Index + $lastInclude.Length, $includeLines)
} else {
$module = [regex]::Match($new, '^-module\([^\r\n]+\)\.[\r\n]+', 'Multiline')
if ($module.Success) {
$new = $new.Insert($module.Index + $module.Length, $includeLines)
} else {
$new = $includeLines + $new
}
}
}
[IO.File]::WriteAllText($full, $new, [Text.UTF8Encoding]::new($false))
$changedFiles++
$removedRecords += $records.Count
}
"header_changed=$headerChanged changed_files=$changedFiles removed_records=$removedRecords target_records=$($recordTarget.Count) skipped=$($skippedNames.Count)"
if ($skippedNames.Count -gt 0) {
"skipped_samples=$([string]::Join(',', $skippedNames))"
}