Public Sub 構造解析_素材を作成

Option Explicit

' ==== 公開エントリーポイント ===========================================
Public Sub 構造解析_素材を作成()
    ' 元:コード(TrimCode, コメント行フラグ, ProcID, LineNo, コードラインID)
    ' 先:構造解析行(1文=1レコード)
    
    Dim db As DAO.Database
    Dim rsSrc As DAO.Recordset
    Dim rsDst As DAO.Recordset
    
    Dim lngCurrentProcID As Long
    Dim strBuf As String                ' 行継続を結合するバッファ
    Dim blnInContinuation As Boolean    ' 継続行の最中か
    
    Dim strSQL As String
    Dim lngTokenSeq As Long             ' @txt の通し番号(ジョブ内で一意)
    
    On Error GoTo EH
    
    Set db = CurrentDb
    
    ' 出力を一旦クリア(必要に応じてコメントアウト)
    db.Execute "DELETE FROM 構造解析行", dbFailOnError
    
    ' 解析対象を取得:コメント行は除外、ProcID→LineNo で安定順
    strSQL = _
        "SELECT コードラインID, ProcID, LineNo, TrimCode, コメント行フラグ " & _
        "FROM コード " & _
        "WHERE Nz(コメント行フラグ, False) = False " & _
        "ORDER BY ProcID, LineNo"
    Set rsSrc = db.OpenRecordset(strSQL, dbOpenSnapshot)
    Set rsDst = db.OpenRecordset("構造解析行", dbOpenDynaset)
    
    lngCurrentProcID = 0
    strBuf = vbNullString
    blnInContinuation = False
    lngTokenSeq = 0
    
    Do While Not rsSrc.EOF
        Dim procID As Long
        Dim lineID As Long
        Dim lineText As String
        
        procID = Nz(rsSrc!ProcID, 0)
        lineID = Nz(rsSrc!コードラインID, 0)
        lineText = Nz(rsSrc!TrimCode, vbNullString)
        
        ' プロシージャが変わったら、未処理バッファを吐き出してリセット
        If lngCurrentProcID <> 0 And procID <> lngCurrentProcID Then
            If Len(strBuf) > 0 Then
                Call 出力_構造解析行(rsDst, lngCurrentProcID, lineID, strBuf, lngTokenSeq)
                strBuf = vbNullString
                blnInContinuation = False
            End If
        End If
        lngCurrentProcID = procID
        
        ' 空行は無視
        If Len(Trim$(lineText)) = 0 Then
            rsSrc.MoveNext
            GoTo ContinueLoop
        End If
        
        ' 行継続の判定(末尾がアンダースコア。末尾空白は無視)
        Dim hasCont As Boolean
        hasCont = 行継続あり(lineText)
        
        ' バッファへ結合(末尾の _ とその手前の空白を除去してから結合)
        If hasCont Then
            strBuf = strBuf & " " & 継続記号を外す(lineText)
            blnInContinuation = True
        Else
            ' 最終行:これで1つの“文候補の束”が完成
            strBuf = strBuf & " " & lineText
            ' ここで整形・分割して出力
            Call 出力_構造解析行(rsDst, procID, lineID, strBuf, lngTokenSeq)
            ' リセット
            strBuf = vbNullString
            blnInContinuation = False
        End If
        
ContinueLoop:
        rsSrc.MoveNext
    Loop
    
    ' ファイル末尾で継続バッファが残っていれば出力
    If Len(strBuf) > 0 Then
        Call 出力_構造解析行(rsDst, lngCurrentProcID, 0, strBuf, lngTokenSeq)
    End If
    
    rsDst.Close
    rsSrc.Close
    Set rsDst = Nothing
    Set rsSrc = Nothing
    Set db = Nothing
    
    MsgBox "構造解析の素材作成が完了しました。", vbInformation
    Exit Sub
    
EH:
    MsgBox "エラー: " & Err.Number & " / " & Err.Description, vbExclamation
End Sub

' ==== 1束のテキスト(継続解決済み)を「構造解析行」へ出力 =================
Private Sub 出力_構造解析行(ByRef rsDst As DAO.Recordset, _
                        ByVal procID As Long, _
                        ByVal anyLineID As Long, _
                        ByVal mergedLine As String, _
                        ByRef tokenSeq As Long)
    ' ここで:文字列→@txt 置換 → 行内コメント削除 → コロン分割 → 空白整え
    
    Dim tokList As Collection
    Dim mapText As String
    Dim work As String
    Dim parts() As String
    Dim i As Long, sentIdx As Long
    
    work = Trim$(mergedLine)
    If Len(work) = 0 Then Exit Sub
    
    ' 1) 文字列を @txtN へ置換し、対応表を作る
    Set tokList = New Collection
    work = 文字列をトークン化(work, tokList, tokenSeq)
    mapText = トークン一覧を文字列化(tokList)
    
    ' 2) 行内コメントを除去(' と Rem)
    work = 行内コメントを削除(work)
    
    ' 空になったら出力不要
    If Len(Trim$(work)) = 0 Then Exit Sub
    
    ' 3) コロンで分割(すでに文字列は @txt に退避済み)
    parts = 文を分割(work)
    
    ' 4) 各文を空白整形して保存
    sentIdx = 0
    For i = LBound(parts) To UBound(parts)
        Dim sent As String
        sent = 空白を整える(parts(i))
        If Len(sent) = 0 Then GoTo NextI
        
        sentIdx = sentIdx + 1
        
        rsDst.AddNew
        rsDst!コードラインID = anyLineID
        rsDst!ProcID = procID
        rsDst!文インデックス = sentIdx
        rsDst!構造解析用コード = sent
        rsDst!文字列トークン一覧 = mapText
        rsDst!生成日時 = Now
        rsDst.Update
NextI:
    Next i
End Sub

' ==== 行継続判定/末尾 _ の除去 ============================================
Private Function 行継続あり(ByVal s As String) As Boolean
    Dim t As String: t = 右端空白を削除(s)
    行継続あり = (Len(t) > 0 And Right$(t, 1) = "_")
End Function

Private Function 継続記号を外す(ByVal s As String) As String
    Dim t As String: t = 右端空白を削除(s)
    If Len(t) > 0 And Right$(t, 1) = "_" Then
        t = Left$(t, Len(t) - 1)
    End If
    継続記号を外す = 右端空白を削除(t)
End Function

Private Function 右端空白を削除(ByVal s As String) As String
    Do While Len(s) > 0
        Dim ch As String: ch = Right$(s, 1)
        If ch = " " Or ch = vbTab Then
            s = Left$(s, Len(s) - 1)
        Else
            Exit Do
        End If
    Loop
    右端空白を削除 = s
End Function

' ==== 文字列 → @txtN 置換("" は1文字の " として扱う) =====================
Private Function 文字列をトークン化(ByVal s As String, _
                                ByRef tokList As Collection, _
                                ByRef tokenSeq As Long) As String
    Dim i As Long, n As Long
    Dim res As String
    Dim inStr As Boolean
    Dim startPos As Long
    
    n = Len(s)
    i = 1
    res = ""
    inStr = False
    startPos = 0
    
    Do While i <= n
        Dim c As String
        c = Mid$(s, i, 1)
        
        If Not inStr Then
            If c = """" Then
                ' 文字列開始
                inStr = True
                startPos = i
            Else
                res = res & c
            End If
            i = i + 1
        Else
            ' 文字列内
            If c = """" Then
                If i < n And Mid$(s, i + 1, 1) = """" Then
                    ' エスケープ("")は1文字の " として継続
                    i = i + 2
                Else
                    ' 文字列終端
                    Dim rawStr As String
                    rawStr = Mid$(s, startPos, i - startPos + 1) ' 引用符込み
                    
                    tokenSeq = tokenSeq + 1
                    Dim tkn As String
                    tkn = "@txt" & CStr(tokenSeq)
                    
                    ' 置換
                    res = res & tkn
                    
                    ' 対応表へ追加
                    tokList.Add rawStr, tkn
                    
                    inStr = False
                    i = i + 1
                End If
            Else
                i = i + 1
            End If
        End If
    Loop
    
    ' 終端抜け(不正な ")は、そのまま返す
    文字列をトークン化 = res
End Function

Private Function トークン一覧を文字列化(ByVal tokList As Collection) As String
    Dim i As Long
    Dim sb As String
    For i = 1 To tokList.Count
        Dim key As String
        key = tokList.Key(i)
        If Len(sb) > 0 Then sb = sb & ";"
        sb = sb & key & "=" & tokList(i)  ' 例:@txt1="ProcName"
    Next i
    トークン一覧を文字列化 = sb
End Function

' ==== 行内コメント削除(' と Rem) =======================================
Private Function 行内コメントを削除(ByVal s As String) As String
    Dim pApo As Long, pRem As Long, t As String
    
    ' アポストロフィ以降
    pApo = InStr(1, s, "'", vbBinaryCompare)
    If pApo > 0 Then s = Left$(s, pApo - 1)
    
    ' Rem は行頭または空白の直後のみ有効とみなす
    ' 簡易に LCase で検索してもよい
    Dim sL As String: sL = LCase$(s)
    pRem = InStr(1, sL, " rem ", vbBinaryCompare)
    If pRem = 1 Then
        s = ""
    ElseIf pRem > 1 Then
        ' 手前が空白なら Rem 以降をカット
        If Mid$(s, pRem - 1, 1) = " " Then
            s = Left$(s, pRem - 1)
        End If
    End If
    
    行内コメントを削除 = Trim$(s)
End Function

' ==== コロン分割(@txt 置換済みなので安全) ===============================
Private Function 文を分割(ByVal s As String) As String()
    Dim parts() As String
    Dim tmp As String
    Dim i As Long, cnt As Long
    Dim startPos As Long
    
    ' 連続コロンや前後空白にも耐えるよう手作り分割
    ReDim parts(0 To 0)
    startPos = 1
    For i = 1 To Len(s)
        If Mid$(s, i, 1) = ":" Then
            tmp = Trim$(Mid$(s, startPos, i - startPos))
            If Len(tmp) > 0 Then
                Call 追加(parts, tmp)
            End If
            startPos = i + 1
        End If
    Next i
    tmp = Trim$(Mid$(s, startPos))
    If Len(tmp) > 0 Then Call 追加(parts, tmp)
    
    文を分割 = parts
End Function

Private Sub 追加(ByRef arr() As String, ByVal v As String)
    Dim n As Long
    If Len(arr(0)) = 0 And UBound(arr) = 0 Then
        arr(0) = v
        Exit Sub
    End If
    n = UBound(arr) + 1
    ReDim Preserve arr(0 To n)
    arr(n) = v
End Sub

' ==== 空白の整え(タブ→空白、連続空白の圧縮、前後空白削除) ==============
Private Function 空白を整える(ByVal s As String) As String
    s = Replace$(s, vbTab, " ")
    ' 連続空白を1つに
    Do While InStr(s, "  ") > 0
        s = Replace$(s, "  ", " ")
    Loop
    空白を整える = Trim$(s)
End Function