行コメントを除去するプログラム

タグの編集
投稿者 heaven  (社会人) 投稿日時 2011/2/14 20:47:16
こんばんわ。
テキストファイルからC言語の「//」のコメント行を削除するプログラム(//から改行コードまで削除)を組んでいますが、自分が組んだプログラムだと最初の行が改行だったりすると無限ループに陥ってしまい回避方法が分かなく困っています。



        Dim read As New System.IO.StreamReader("C:\test.txt", System.Text.Encoding.GetEncoding("shift_jis"))
        Dim text As String = read.ReadToEnd()

        read.Close()


        If System.Text.RegularExpressions.Regex.IsMatch(text, ".*//.*"Then

            Const SearchWord As String = "//"
            Const SearchEndWord As String = vbCrLf


            Dim FoundIndex As Integer
            Dim FoundEnd As Integer
            Dim CutComment As String



            While 0 <= FoundIndex

                FoundIndex = text.IndexOf(SearchWord)
                FoundEnd = text.IndexOf(SearchEndWord)


                If FoundIndex > -1 Then

                    If FoundEnd = -1 Then 'EOFの時 

                        CutComment = text.Substring(FoundIndex)
                        text = Replace(text, CutComment, String.Empty)

                    Else

                        CutComment = Mid(text, FoundIndex + 1, FoundEnd)
                        text = Replace(text, CutComment, String.Empty)
                    End If

                Else

                    Exit While

                End If

            End While

        End If



上手く修正できる方がおられましたらよろしくお願いします。
投稿者 shu  (社会人) 投稿日時 2011/2/14 22:48:58
こんなでしょうか?


Imports System.Text.RegularExpressions
Imports System.IO
Imports System.Text

    Private Sub ~

    '--- ファイル読みはとりあえずそのまま
        Dim read As New System.IO.StreamReader("TextFile1.txt", System.Text.Encoding.GetEncoding("shift_jis"))
        Dim text As String = read.ReadToEnd()

        read.Close()

    '--- 行頭 + スペースの繰り返し + // + 何らかの文字列 + 行末  用パターン
        Dim reg As New Regex("^\s*//.*$")  
    '--- 1行づつ読むためのStringReader(ファイル読み込みストリーム自体を使ってもいいかも)
        Dim stm As New StringReader(text)
        '--- 結果用のStringBuilder
        Dim strOut As New StringBuilder
    '--- 行データ格納用
        Dim strLine As String
        '--- とりあえず1行読む
        strLine = stm.ReadLine()

        Do While strLine IsNot Nothing
      '--- パターンにマッチしない行だけ出力用StringBuilderに連結
            If Not reg.Match(strLine).Success Then
                strOut.AppendLine(strLine)
            End If
      '--- 次の行をよむ
            strLine = stm.ReadLine
        Loop
     stm.close
        '--- 結果をTextBox1へ表示
        TextBox1.Text = strOut.ToString

    End Sub
投稿者 るきお  (社会人) 投稿日時 2011/2/14 22:53:50
ソースコードからコメントを取り除くのは実は難しいです。
95%の単純なコメントは苦も無く取り去ることができますが、やっかいなパターンがあります。
C言語でいうと、次のような行のコメントだけ取り除くロジックが書けますか?
string str = "//はコメントの始まりです。" //""//""


\でクォーテーションがエスケープされているケースもあります。
さらに、今回はC言語ということですから対象外かもしれませんがC#を考えると、@"…"のケースやXMLコメントのケースなど考慮すべきことがたくさんあります。


という困難な状況はひとまず置いておいて95%の単純なコメントを除去するプログラムです。
改行の位置を自分で調べるのではなく、最初から1行ずつ読んできたほうが楽です。
なので、修正というかほとんど作り変えてしまいました。

  
Dim sjis As System.Text.Encoding = System.Text.Encoding.GetEncoding("shift_jis")
Dim reader As New IO.StreamReader("C:\test\test.txt", sjis)
Dim writer As New IO.StreamWriter("C:\test\result.txt"False, sjis)

Do Until reader.EndOfStream

    '対象から1行読む 
    Dim sourceLine As String = reader.ReadLine

    '//の位置を取得 
    Dim pos As Integer = sourceLine.IndexOf("//")

    Dim targetLine As String
    If pos >= 0 Then
        '//が存在するならば、そこまでの文字列を切り抜く。 
        targetLine = sourceLine.Substring(0, pos)
    Else
        '//が存在しないならば、読み込んだ行全体が書き込む対象となる。 
        targetLine = sourceLine
    End If

    '書き込み 
    writer.WriteLine(targetLine)
Loop

reader.Close()
reader.Dispose()

writer.Close()
writer.Dispose()
投稿者 るきお(管理者)  (社会人) 投稿日時 2011/2/14 23:10:15
shuさんとダブルで被っちゃいました…。
いつも投稿ありがとうございます。
投稿者 shu  (社会人) 投稿日時 2011/2/15 08:16:05
被ってしまいましたね。


ちなみに私のほうはコメント行を削除、るきおさんの方はコメントを削除なので
用途に合わせて使い分けて下さい。両方なら合わせてみてください > heavenさん
投稿者 (削除されました)  () 投稿日時 2011/2/15 09:35:02
(削除されました)
投稿者 (削除されました)  () 投稿日時 2011/2/15 12:21:56
(削除されました)
投稿者 heaven  (社会人) 投稿日時 2011/2/15 12:23:20
お返事ありがとうございます。
コメントの削除は「Regex.Replace(test, "/\*([^*]|\*[^/])*\*/", String.Empty)」で割と簡単に削除できましたが、行コメントの削除となると意外と難しいかったです^^;

対象ソースには、行コメントとの直前にもコードが綴られていたりする場合もあるのと、
るきおさんの、「StreamReader」では既に編集済みのソースコードを格納した変数から直接編集できなかったので、shuさんとるきおさんのコードを参考にミックスした感じで作らせていただきました。

本当に助かりました。m(_ _"m)


一応作成したコードを載せておきます。
        Dim read As New System.IO.StreamReader("C:\test.txt", System.Text.Encoding.GetEncoding("shift_jis"))
        Dim text As String = read.ReadToEnd()



        If System.Text.RegularExpressions.Regex.IsMatch(text, ".*//.*"Then  'コメント有無チェック 

            Dim lst As New List(Of String)()  '読込みデータ格納用 


            Dim stm As New StringReader(text) '1行づつ読むためのStringReader 
            Dim strLine As String '行データ格納用 

            strLine = stm.ReadLine() 'とりあえず1行読む 


            Do While strLine IsNot Nothing

                Dim targetLine As String  '切り抜き用変数 

                '//の位置を取得  
                Dim pos As Integer = strLine.IndexOf("//")

                If pos >= 0 Then
                    '//が存在するならば、そこまでの文字列を切り抜く。  
                    targetLine = strLine.Substring(0, pos)
                    lst.Add(targetLine)

                Else
                    '//が存在しないならば、読み込んだ行全体が書き込む対象となる。  
                    targetLine = strLine
                    lst.Add(targetLine)

                End If

                '次の行をよむ 
                strLine = stm.ReadLine

            Loop

            '格納したデータを改行コードで結合 
            text = String.Join(vbCrLf, lst.ToArray)

        End If

        textbox1.text = text  '結果をTextBox1へ表示 

投稿者 よねKEN  (社会人) 投稿日時 2011/2/16 02:03:16
> コメントの削除は「Regex.Replace(test, "/\*([^*]|\*[^/])*\*/", String.Empty)」で割と簡単に削除できましたが、行コメントの削除となると意外と難しいかったです^^;

TextBox1.Text = Regex.Replace(text, "//.*$", vbCrLf, RegexOptions.Multiline)

とするだけで削除できますよ。
(厳密にはtextのデータの最後が改行ではない場合に、
 余計な改行が一つ付加された状態に置換されますが)

余談ですが、//コメントはC言語のコメントではなくC++のコメントですね。
投稿者 (削除されました)  () 投稿日時 2011/2/16 10:03:39
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2011/2/16 10:07:55
> 余談ですが、//コメントはC言語のコメントではなくC++のコメントですね。
C 言語における「//」の正式サポートは、
 『ISO/IEC 9899:1999 - Programming Language C』
 『JIS X 3010:2003 - プログラム言語C』
からのようですね。上記の 6.4.9 を参照。

逆に言うと、それ以前の仕様(K&R, C89/ISO C90, C95) においては、
// コメントは定義されていないようです。もっとも、仕様上には無くとも
コンパイラ側では使えるように拡張実装されているケースもあるようですが。


> TextBox1.Text = Regex.Replace(text, "//.*$", vbCrLf, RegexOptions.Multiline)

「char *url = "http://www.google.co.jp";」が
「char *url = "http:」になってしまうのでは?


厳密に処理するなら、長いマクロ処理などで使われる『改行前の\』も
考慮する必要があるかも知れません。

たとえば、「char *s = "yen = 100; // money : 100";」という一行のコードを
「char *s = "\」「yen = 100; // money : 100\」「";」という三行で書かれる可能性もあるわけで。

---
るきおさん宛:

本文の行末に\記号があった場合、プレビュー画面では正しく表示されますが、
掲示板への投稿結果では「\ + 改行」が削除され、一行に繋がってしまうようです。
投稿者 よねKEN  (社会人) 投稿日時 2011/2/16 13:06:34
> > 余談ですが、//コメントはC言語のコメントではなくC++のコメントですね。
> C 言語における「//」の正式サポートは、
>  『ISO/IEC 9899:1999 - Programming Language C』
>  『JIS X 3010:2003 - プログラム言語C』
> からのようですね。上記の 6.4.9 を参照。

フォローありがとうございます。
最近の仕様ならひょっとしたらC言語に取り込まれているかもと、
さらっとは原典を探してみたものの、見つからなかったのですが、
まぁ大丈夫かと安易にツッコミ入れてしまいました。
(誰かがフォローしてくれることもひそかに期待していましたが:-)

> 「char *url = "http://www.google.co.jp";」が
>「char *url = "http:」になってしまうのでは?

はい、そうなります。
るきおさんの投稿(投稿日時 2011/2/14 22:53:50)で指摘済みだったのと
その後の質問者さんの提示コードではそこまでは考慮しない形でしたので、
私の提示例も文字列リテラルの中かどうかといったことを考慮していません。

> 厳密に処理するなら

C言語の言語仕様の詳細を調べないとはっきりとは言えませんが、
コンパイルが通るソースであることを前提にするとしても、
厳密にやるとなると、字句解析と構文解析まではやらないといけないと思います。
(言語仕様によっては字句解析だけでいけるかも)
投稿者 shu  (社会人) 投稿日時 2011/2/17 00:38:14
これでだいぶましになったと思う。まだ抜けはあると思います。
TextBox1:入力
TextBox2:出力
にしてあります。

        Dim reg2 As New Regex("^(?<PRE>[^""]*"")(?<AFT>.*)$", RegexOptions.Compiled)
        Dim reg3 As New Regex("^(?<PRE>[^/]*)/(?<C2>[/*])(?<AFT>.*)$", RegexOptions.Compiled)
        Dim reg4 As New Regex("^(?<PRE>[^*]*)\*/(?<AFT>.*)$", RegexOptions.Compiled)
        Dim reg5 As New Regex("(?<PRE>(^.*?[^\\]|^)"")(?<AFT>.*)$", RegexOptions.Compiled)
        Dim rd As New StringReader(TextBox1.Text)
        Dim wt As New StringBuilder
        Dim blnCom = False
        Dim blnStr = False
        Dim m As Match

        Dim strLine = rd.ReadLine
        Do While strLine IsNot Nothing
            Do While strLine.Length > 0
                '--- コメント中
                If blnCom Then
                    m = reg4.Match(strLine)
                    If m.Success Then
                        blnCom = False
                        strLine = m.Groups("AFT").Value
                    Else
                        strLine = String.Empty
                    End If
                    Continue Do
                End If
                '--- 文字列中
                If blnStr Then
                    m = reg5.Match(strLine)
                    If m.Success Then
                        blnStr = False
                        wt.Append(m.Groups("PRE").Value)
                        strLine = m.Groups("AFT").Value
                        If strLine.Length = 0 Then
                            wt.AppendLine()
                        End If
                    Else
                        wt.AppendLine(strLine)
                        strLine = String.Empty
                    End If
                    Continue Do
                End If
                '--- 文字列の開始検知
                m = reg2.Match(strLine)
                If m.Success Then
                    blnStr = True
                    wt.Append(m.Groups("PRE").Value)
                    strLine = m.Groups("AFT").Value
                    If strLine.Length = 0 Then
                        wt.AppendLine()
                    End If
                    Continue Do
                End If
                '--- コメントの開始検知
                m = reg3.Match(strLine)
                If m.Success Then
                    wt.Append(m.Groups("PRE").Value)
                    Dim strC2 = m.Groups("C2").Value
                    If strC2 = "/" Then
                        strLine = String.Empty
                        wt.AppendLine()
                        Continue Do
                    Else
                        blnCom = True
                        strLine = m.Groups("AFT").Value
                        If strLine.Length = 0 Then
                            wt.AppendLine()
                        End If
                        Continue Do
                    End If
                End If
                '--- 通常行
                wt.AppendLine(strLine)
                strLine = String.Empty
            Loop
            strLine = rd.ReadLine
        Loop

        TextBox2.Text = wt.ToString
        rd.Close()
        rd.Dispose()