Option Explicit
' 呼び出し監査&完全な呼び出し表。t01a の TrimCode を一から機械パース。VBA(Dictionary+RegExp+DAO)のみ。
' 定数直値: dbOpenSnapshot=4, dbOpenDynaset=2, dbAppendOnly=8, dbFailOnError=128
' RunCallAudit … 既存 呼出先プロシージャID と突合し z_呼出監査 に差分+分類を出力。
' BuildCallTable … 分類=内部呼び だけを呼元→呼先の辺として z_呼出表 に出力(潜り駆動用・既存ID非依存)。
' 2026/07/11 拡張4:識別子に日本語を許可/名前照合は大文字小文字無視/連鎖末尾メソッド a.b.c(...) を名前一意で解決。
' 識別子文字クラス IDC は BuildRe で ChrW により明示コードポイントで組み立てる(範囲逆転・文字化けの根絶)。
Private mByName As Object
Private mByModName As Object
Private mModuleSet As Object
Private mInitOf As Object
Private mNameOf As Object
Private mModOf As Object
Private mStop As Object
Private mVarType As Object
Private mVarExt As Object
Private mModVarType As Object
Private mModVarExt As Object
Private mKindOf As Object
Private reStr As Object, reDecl As Object, reNew As Object, reQual As Object
Private reCall As Object, reParen As Object, reAddr As Object, reAppRun As Object, reCbn As Object, reLead As Object
Private reAsType As Object, reSetNew As Object, reSetCreate As Object, reDeclKind As Object, reAnyDecl As Object, reDotCall As Object
Private mDb As Object
Private mOut As Object
Public Sub BuildCallTable()
Set mDb = CurrentDb
BuildRe
BuildSym
BuildVarTypes
BuildStop
EnsureCallTable
Dim tOut As Object
Set tOut = mDb.OpenRecordset("z_呼出表", 2, 8)
Dim rs As Object
Set rs = mDb.OpenRecordset( _
"SELECT プロシージャID, コードラインID, TrimCode FROM t01a_コード " & _
"WHERE コメント行=False AND 空白行=False ORDER BY プロシージャID, CLng(コードラインID)", 4)
Do While Not rs.EOF
CollectCalls tOut, NzL(rs.Fields("プロシージャID").Value), NzL(rs.Fields("コードラインID").Value), _
SS(rs.Fields("TrimCode").Value)
rs.MoveNext
Loop
rs.Close
tOut.Close
WriteCallTableSummary
End Sub
Private Sub CollectCalls(ByRef tOut As Object, ByVal pid As Long, ByVal cid As Long, ByVal raw As String)
Dim curName As String: curName = "": If mNameOf.exists(pid) Then curName = mNameOf(pid)
Dim curMod As String: curMod = "": If mModOf.exists(pid) Then curMod = mModOf(pid)
Dim clean As String: clean = StripStr(raw)
If reDecl.Test(clean) Then Exit Sub
Dim resolved As Object: Set resolved = CreateObject("Scripting.Dictionary")
Dim ambig As Object: Set ambig = CreateObject("Scripting.Dictionary")
Dim ambCls As Object: Set ambCls = CreateObject("Scripting.Dictionary")
DetectLine pid, curMod, curName, clean, raw, resolved, ambig, ambCls
Dim emitted As Object: Set emitted = CreateObject("Scripting.Dictionary")
Dim k As Variant, callee As Long, kc As String
For Each k In resolved.keys
callee = CLng(k)
If Not emitted.exists(callee) Then
kc = KindClass(callee)
If kc = "内部呼び" Then
WriteEdge tOut, pid, curMod, curName, cid, callee, CStr(resolved(k)), "呼び"
emitted(callee) = True
ElseIf kc = "参照" Then
WriteEdge tOut, pid, curMod, curName, cid, callee, CStr(resolved(k)), "Property"
emitted(callee) = True
End If
End If
Next
For Each k In ambig.keys
If ambCls.exists(k) Then
If CLng(ambig(k)) = 1 And (ambCls(k) = "要判定" Or ambCls(k) = "参照") Then
Dim arr() As String: arr = Split(CStr(mByName(LK(CStr(k)))(1)), "|")
callee = CLng(arr(0))
If Not emitted.exists(callee) Then
kc = KindClass(callee)
If kc = "内部呼び" And ambCls(k) = "要判定" Then
WriteEdge tOut, pid, curMod, curName, cid, callee, "名前一意", "呼び"
emitted(callee) = True
ElseIf kc = "参照" And ambCls(k) = "参照" Then
WriteEdge tOut, pid, curMod, curName, cid, callee, "名前一意", "Property"
emitted(callee) = True
End If
End If
End If
End If
Next
End Sub
Private Sub EnsureCallTable()
On Error Resume Next
mDb.TableDefs.Delete "z_呼出表"
On Error GoTo 0
mDb.Execute "CREATE TABLE z_呼出表 (" & _
"呼元PrcID LONG, 呼元Mdl TEXT(120), 呼元Prc TEXT(120), コードラインID LONG, " & _
"呼先PrcID LONG, 呼先Mdl TEXT(120), 呼先Prc TEXT(120), 検出パターン TEXT(30), " & _
"呼先種別 TEXT(12), 潜り TEXT(8))", 128
End Sub
Private Sub WriteEdge(ByRef tOut As Object, ByVal srcPid As Long, ByVal srcMod As String, ByVal srcPrc As String, _
ByVal cid As Long, ByVal dstPid As Long, ByVal pat As String, ByVal kind As String)
tOut.AddNew
tOut.Fields("呼元PrcID").Value = srcPid
tOut.Fields("呼元Mdl").Value = srcMod
tOut.Fields("呼元Prc").Value = srcPrc
tOut.Fields("コードラインID").Value = cid
tOut.Fields("呼先PrcID").Value = dstPid
tOut.Fields("呼先Mdl").Value = NameMod(dstPid)
tOut.Fields("呼先Prc").Value = NameSafe(dstPid)
tOut.Fields("検出パターン").Value = pat
tOut.Fields("呼先種別").Value = kind
tOut.Fields("潜り").Value = "潜る"
tOut.Update
End Sub
Private Sub WriteCallTableSummary()
On Error Resume Next
mDb.Execute "DROP TABLE z_calltable_sum", 128
On Error GoTo 0
mDb.Execute "CREATE TABLE z_calltable_sum (item TEXT(30), val LONG)", 128
PutCTSum "edges_total", DCount("*", "z_呼出表")
PutCTSum "edges_call", DCount("*", "z_呼出表", "呼先種別='呼び'")
PutCTSum "edges_prop", DCount("*", "z_呼出表", "呼先種別='Property'")
PutCTSum "edges_dive", DCount("*", "z_呼出表", "潜り='潜る'")
PutCTSum "edges_stop", DCount("*", "z_呼出表", "潜り='止め'")
End Sub
Private Sub PutCTSum(ByVal item As String, ByVal val As Long)
mDb.Execute "INSERT INTO z_calltable_sum (item, val) VALUES ('" & item & "', " & val & ")", 128
End Sub
Public Function CmpPing() As String
CmpPing = "pong"
End Function
Public Sub ClassifyProcsT03a(Optional ByVal doWrite As Boolean = False)
On Error GoTo EH
Set mDb = CurrentDb
BuildRe
BuildSym
BuildVarTypes
BuildStop
Dim callSet As Object: Set callSet = CreateObject("Scripting.Dictionary")
Dim resSet As Object: Set resSet = CreateObject("Scripting.Dictionary")
Dim dynSet As Object: Set dynSet = CreateObject("Scripting.Dictionary")
Dim fireSet As Object: Set fireSet = CreateObject("Scripting.Dictionary")
Dim yoSet As Object: Set yoSet = CreateObject("Scripting.Dictionary")
Dim reEval As Object: Set reEval = NewRe("Evaluate|Application\.Run|CallByName")
Dim rslv As Object: Set rslv = CreateObject("Scripting.Dictionary")
Dim amb As Object: Set amb = CreateObject("Scripting.Dictionary")
Dim ambC As Object: Set ambC = CreateObject("Scripting.Dictionary")
Dim rs As Object
Set rs = mDb.OpenRecordset( _
"SELECT プロシージャID, TrimCode FROM t01a_コード " & _
"WHERE コメント行=False AND 空白行=False AND プロシージャID IS NOT NULL " & _
"ORDER BY プロシージャID, CLng(コードラインID)", 4)
Do While Not rs.EOF
Dim ppid As Long: ppid = NzL(rs.Fields(0).Value)
Dim raw As String: raw = SS(rs.Fields(1).Value)
Dim clean As String: clean = StripStr(raw)
If ppid > 0 And Len(clean) > 0 Then
If reEval.Test(clean) Then dynSet(ppid) = True
If reNew.Test(clean) Then
Dim mn As Object, mt As Object
Set mn = reNew.Execute(clean)
For Each mt In mn
If mInitOf.exists(LK(mt.SubMatches(0))) Then fireSet(ppid) = True
Next
End If
If Not reDecl.Test(clean) Then
Dim cMod As String: cMod = "": If mModOf.exists(ppid) Then cMod = mModOf(ppid)
Dim cName As String: cName = "": If mNameOf.exists(ppid) Then cName = mNameOf(ppid)
rslv.RemoveAll: amb.RemoveAll: ambC.RemoveAll
DetectLine ppid, cMod, cName, clean, raw, rslv, amb, ambC
If rslv.Count > 0 Then resSet(ppid) = True
Dim kk As Variant
For Each kk In amb.keys
If ambC.exists(kk) Then
If ambC(kk) = "要判定" Then
yoSet(ppid) = True
If CLng(amb(kk)) = 1 Then
Dim arrc() As String: arrc = Split(CStr(mByName(LK(CStr(kk)))(1)), "|")
Dim cpid As Long: cpid = CLng(arrc(0))
If KindClass(cpid) = "内部呼び" Then callSet(ppid) = True
End If
End If
End If
Next
End If
End If
rs.MoveNext
Loop
rs.Close
Dim cCall As Long, cLeaf As Long, cFire As Long, cUndef As Long, cConf As Long, cTotal As Long
Dim wrs As Object
Set wrs = mDb.OpenRecordset("SELECT PRC_ID, Prc名, 呼び出しあり, 呼び出しなし, 自動発火, 確認済み FROM t03a_プロシージャ名", 2)
Do While Not wrs.EOF
Dim pid As Long: pid = NzL(wrs.Fields("PRC_ID").Value)
Dim pn As String: pn = SS(wrs.Fields("Prc名").Value)
If pn <> "(モジュールレベル)" Then
cTotal = cTotal + 1
Dim conf As Boolean: conf = (wrs.Fields("確認済み").Value = True)
If conf Then
cConf = cConf + 1
If wrs.Fields("呼び出しあり").Value = True Then cCall = cCall + 1 Else cLeaf = cLeaf + 1
If wrs.Fields("自動発火").Value = True Then cFire = cFire + 1
Else
Dim isFire As Boolean: isFire = fireSet.exists(pid)
Dim isCall As Boolean: isCall = (callSet.exists(pid) Or resSet.exists(pid) Or dynSet.exists(pid) Or isFire)
Dim isUndef As Boolean: isUndef = ((Not isCall) And yoSet.exists(pid))
If isCall Then
cCall = cCall + 1
ElseIf isUndef Then
cUndef = cUndef + 1
Else
cLeaf = cLeaf + 1
End If
If isFire Then cFire = cFire + 1
If doWrite Then
wrs.Edit
wrs.Fields("呼び出しあり").Value = isCall
wrs.Fields("呼び出しなし").Value = ((Not isCall) And (Not isUndef))
wrs.Fields("自動発火").Value = isFire
wrs.Update
End If
End If
End If
wrs.MoveNext
Loop
wrs.Close
On Error Resume Next
mDb.Execute "DROP TABLE z_class_summary", 128
On Error GoTo EH
mDb.Execute "CREATE TABLE z_class_summary (item TEXT(30), val LONG)", 128
PutSummary "call", cCall
PutSummary "leaf", cLeaf
PutSummary "fire", cFire
PutSummary "undef", cUndef
PutSummary "confirmed", cConf
PutSummary "total_real", cTotal
PutSummary "wrote", CLng(doWrite)
PutSummary "v_call", DCount("*", "t03a_プロシージャ名", "呼び出しあり=True")
PutSummary "v_leaf", DCount("*", "t03a_プロシージャ名", "呼び出しなし=True")
PutSummary "v_fire", DCount("*", "t03a_プロシージャ名", "自動発火=True")
PutSummary "v_contra", DCount("*", "t03a_プロシージャ名", "呼び出しあり=True AND 呼び出しなし=True")
PutSummary "v_conf", DCount("*", "t03a_プロシージャ名", "確認済み=True")
PutSummary "v_p737nashi", DCount("*", "t03a_プロシージャ名", "PRC_ID=737 AND 呼び出しなし=True")
Exit Sub
EH:
On Error Resume Next
mDb.Execute "CREATE TABLE z_class_summary (item TEXT(30), val LONG)", 128
PutSummary "error_num", Err.Number
End Sub
Private Sub PutSummary(ByVal item As String, ByVal val As Long)
mDb.Execute "INSERT INTO z_class_summary (item, val) VALUES ('" & item & "', " & val & ")", 128
End Sub
Public Sub ClassifyProcsT03a_Dry()
ClassifyProcsT03a False
End Sub
Public Sub ClassifyProcsT03a_Write()
ClassifyProcsT03a True
End Sub
Public Sub DumpCallDiff()
On Error GoTo EH
Set mDb = CurrentDb
BuildCallTable
Dim callSet As Object: Set callSet = CreateObject("Scripting.Dictionary")
Dim dynSet As Object: Set dynSet = CreateObject("Scripting.Dictionary")
Dim fireSet As Object: Set fireSet = CreateObject("Scripting.Dictionary")
Dim yoSet As Object: Set yoSet = CreateObject("Scripting.Dictionary")
Dim rs As Object
Set rs = mDb.OpenRecordset("SELECT DISTINCT 呼元PrcID FROM z_呼出表", 4)
Do While Not rs.EOF: callSet(NzL(rs.Fields(0).Value)) = True: rs.MoveNext: Loop
rs.Close
Set rs = mDb.OpenRecordset("SELECT DISTINCT プロシージャID FROM z_呼出監査 WHERE 分類='要判定'", 4)
Do While Not rs.EOF: yoSet(NzL(rs.Fields(0).Value)) = True: rs.MoveNext: Loop
rs.Close
Dim reEval As Object: Set reEval = NewRe("Evaluate|Application\.Run|CallByName")
Set rs = mDb.OpenRecordset( _
"SELECT プロシージャID, TrimCode FROM t01a_コード " & _
"WHERE コメント行=False AND 空白行=False AND プロシージャID IS NOT NULL " & _
"ORDER BY プロシージャID, CLng(コードラインID)", 4)
Do While Not rs.EOF
Dim ppid As Long: ppid = NzL(rs.Fields(0).Value)
Dim clean As String: clean = StripStr(SS(rs.Fields(1).Value))
If ppid > 0 And Len(clean) > 0 Then
If reEval.Test(clean) Then dynSet(ppid) = True
If reNew.Test(clean) Then
Dim mn As Object, mt As Object
Set mn = reNew.Execute(clean)
For Each mt In mn
If mInitOf.exists(LK(mt.SubMatches(0))) Then fireSet(ppid) = True
Next
End If
End If
rs.MoveNext
Loop
rs.Close
On Error Resume Next
mDb.Execute "DROP TABLE z_call_diff", 128
On Error GoTo EH
mDb.Execute "CREATE TABLE z_call_diff (PRC_ID LONG, Mdl TEXT(120), Prc TEXT(120), " & _
"ps_call LONG, edge LONG, dyn LONG, fire LONG, yo LONG, direction TEXT(20))", 128
Dim wrs As Object
Set wrs = mDb.OpenRecordset("SELECT PRC_ID, Mdl名, Prc名, 呼び出しあり, 確認済み FROM t03a_プロシージャ名", 4)
Do While Not wrs.EOF
Dim pid As Long: pid = NzL(wrs.Fields("PRC_ID").Value)
Dim pn As String: pn = SS(wrs.Fields("Prc名").Value)
Dim md As String: md = SS(wrs.Fields("Mdl名").Value)
If pn <> "(モジュールレベル)" Then
Dim psC As Boolean: psC = (wrs.Fields("呼び出しあり").Value = True)
Dim eg As Boolean: eg = callSet.exists(pid)
Dim dy As Boolean: dy = dynSet.exists(pid)
Dim fr As Boolean: fr = fireSet.exists(pid)
Dim yo As Boolean: yo = yoSet.exists(pid)
Dim vbaC As Boolean: vbaC = (eg Or dy Or fr)
If psC <> vbaC Then
Dim dir As String
If psC And Not vbaC Then dir = "PS呼び_VBA葉" Else dir = "PS葉_VBA呼び"
mDb.Execute "INSERT INTO z_call_diff (PRC_ID, Mdl, Prc, ps_call, edge, dyn, fire, yo, direction) VALUES (" & _
pid & ", '" & Replace(md, "'", "''") & "', '" & Replace(pn, "'", "''") & "', " & _
Abs(psC) & ", " & Abs(eg) & ", " & Abs(dy) & ", " & Abs(fr) & ", " & Abs(yo) & ", '" & dir & "')", 128
End If
End If
wrs.MoveNext
Loop
wrs.Close
Exit Sub
EH:
End Sub
Public Sub RunCallAudit()
On Error GoTo EH
Set mDb = CurrentDb
BuildRe
BuildSym
BuildVarTypes
BuildStop
EnsureAuditTable
Set mOut = mDb.OpenRecordset("z_呼出監査", 2, 8)
Dim rs As Object
Set rs = mDb.OpenRecordset( _
"SELECT プロシージャID, コードラインID, TrimCode, 呼出先プロシージャID FROM t01a_コード " & _
"WHERE コメント行=False AND 空白行=False ORDER BY プロシージャID, CLng(コードラインID)", 4)
Do While Not rs.EOF
AuditLine NzL(rs.Fields("プロシージャID").Value), NzL(rs.Fields("コードラインID").Value), _
SS(rs.Fields("TrimCode").Value), NzL(rs.Fields("呼出先プロシージャID").Value)
rs.MoveNext
Loop
rs.Close
mOut.Close
Set mOut = Nothing
Exit Sub
EH:
On Error Resume Next
If Not mOut Is Nothing Then
mOut.AddNew
mOut.Fields("判定").Value = "エラー"
mOut.Fields("TrimCode").Value = "実行時: " & Err.Number & " " & Err.Description
mOut.Update
mOut.Close
End If
End Sub
Private Function LK(ByVal s As String) As String
LK = LCase$(s)
End Function
Private Sub BuildRe()
Dim IDC As String
IDC = "A-Za-z0-9_" & ChrW(&H3041) & "-" & ChrW(&H3096) _
& ChrW(&H30A1) & "-" & ChrW(&H30FA) & ChrW(&H30FC) & ChrW(&H3005) _
& ChrW(&H4E00) & "-" & ChrW(&H9FFF)
Set reStr = NewRe("""[^""]*""")
Set reDecl = NewRe("^\s*(Public |Private |Friend )?(Static )?(Sub|Function|Property (Get|Let|Set))\b")
Set reNew = NewRe("\bNew\s+([" & IDC & "]+)")
Set reQual = NewRe("([" & IDC & "]+)\.([" & IDC & "]+)")
Set reCall = NewRe("\bCall\s+([" & IDC & "]+)(\.([" & IDC & "]+))?")
Set reParen = NewRe("(^|[^" & IDC & ".])([" & IDC & "]+)\s*\(")
Set reAddr = NewRe("\bAddressOf\s+([" & IDC & "]+)")
Set reAppRun = NewRe("Application\.Run\s*\(?\s*""([^""]+)""")
Set reCbn = NewRe("CallByName\s*\(\s*[^,]+,\s*""([^""]+)""")
Set reLead = NewRe("^\s*([" & IDC & "]+)\s*(.?)")
Set reAsType = NewRe("([" & IDC & "]+)\s+As\s+(?:New\s+)?([" & IDC & "]+)")
Set reSetNew = NewRe("\bSet\s+([" & IDC & "]+)\s*=\s*New\s+([" & IDC & "]+)")
Set reSetCreate = NewRe("\bSet\s+([" & IDC & "]+)\s*=\s*CreateObject")
Set reDeclKind = NewRe("^\s*(?:Public |Private |Friend )?(?:Static )?(Sub|Function|Property)\b")
Set reAnyDecl = NewRe("^\s*(Public |Private |Global |Friend |Dim |Static )")
Set reDotCall = NewRe("\.([" & IDC & "]+)\s*\(")
End Sub
Private Function NewRe(ByVal pat As String) As Object
Set NewRe = CreateObject("VBScript.RegExp")
NewRe.Global = True
NewRe.IgnoreCase = False
NewRe.Pattern = pat
End Function
Private Sub BuildSym()
Set mByName = CreateObject("Scripting.Dictionary")
Set mByModName = CreateObject("Scripting.Dictionary")
Set mModuleSet = CreateObject("Scripting.Dictionary")
Set mInitOf = CreateObject("Scripting.Dictionary")
Set mNameOf = CreateObject("Scripting.Dictionary")
Set mModOf = CreateObject("Scripting.Dictionary")
Dim rs As Object
Set rs = mDb.OpenRecordset("SELECT PRC_ID, Mdl名, Prc名 FROM t03a_プロシージャ名", 4)
Do While Not rs.EOF
Dim pid As Long: pid = NzL(rs.Fields("PRC_ID").Value)
Dim m As String: m = SS(rs.Fields("Mdl名").Value)
Dim pn As String: pn = SS(rs.Fields("Prc名").Value)
mModuleSet(LK(m)) = True
mNameOf(pid) = pn
mModOf(pid) = m
If LK(pn) = "class_initialize" Then mInitOf(LK(m)) = pid
mByModName(LK(m) & "|" & LK(pn)) = pid
If Len(pn) >= 3 Then
If Not mByName.exists(LK(pn)) Then mByName.Add LK(pn), New Collection
mByName(LK(pn)).Add pid & "|" & LK(m)
End If
rs.MoveNext
Loop
rs.Close
End Sub
Private Sub BuildVarTypes()
Set mVarType = CreateObject("Scripting.Dictionary")
Set mVarExt = CreateObject("Scripting.Dictionary")
Set mModVarType = CreateObject("Scripting.Dictionary")
Set mModVarExt = CreateObject("Scripting.Dictionary")
Set mKindOf = CreateObject("Scripting.Dictionary")
Dim rs As Object
Set rs = mDb.OpenRecordset( _
"SELECT プロシージャID, TrimCode FROM t01a_コード " & _
"WHERE コメント行=False AND 空白行=False ORDER BY プロシージャID, CLng(コードラインID)", 4)
Do While Not rs.EOF
Dim pid As Long: pid = NzL(rs.Fields("プロシージャID").Value)
Dim clean As String: clean = StripStr(SS(rs.Fields("TrimCode").Value))
If pid > 0 And Len(clean) > 0 Then CollectDecls pid, clean
rs.MoveNext
Loop
rs.Close
End Sub
Private Sub CollectDecls(ByVal pid As Long, ByVal clean As String)
Dim mk As Object
If reDeclKind.Test(clean) Then
Set mk = reDeclKind.Execute(clean)
If mk(0).SubMatches(0) = "Property" Then mKindOf(pid) = "P" Else mKindOf(pid) = "S"
End If
Dim pn As String: pn = "": If mNameOf.exists(pid) Then pn = mNameOf(pid)
Dim mdl As String: mdl = "": If mModOf.exists(pid) Then mdl = mModOf(pid)
Dim isModLevel As Boolean: isModLevel = (pn = "(モジュールレベル)")
Dim mts As Object, mt As Object, vn As String, tp As String
If isModLevel Or reAnyDecl.Test(clean) Or reDecl.Test(clean) Then
Set mts = reAsType.Execute(clean)
For Each mt In mts
vn = LK(mt.SubMatches(0)): tp = mt.SubMatches(1)
RegVar pid, mdl, isModLevel, vn, tp
Next
End If
Set mts = reSetNew.Execute(clean)
For Each mt In mts
vn = LK(mt.SubMatches(0)): tp = mt.SubMatches(1)
RegVar pid, mdl, isModLevel, vn, tp
Next
Set mts = reSetCreate.Execute(clean)
For Each mt In mts
vn = LK(mt.SubMatches(0))
RegVarExt pid, mdl, isModLevel, vn, "CreateObject"
Next
End Sub