challenge コメントの削除

ソースコードからコメント部分を削除するプログラム decomment を書いてください.
すくなくとも,decomment を記述したのと同じ言語で書かれているソースコードが
扱えるようにしてください.



Posted feedbacks - Flatten

Nested Hidden

一番のりを狙って書きました。起動パラメータに与えられたJavaのソースファイルからコメントを削除し、".out" 拡張子つきのファイルに書き込みます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.regex.*;
import java.io.*;

public class Decomment {

    static final Pattern COMMENT1 = Pattern.compile("/\\*.*?\\*/", Pattern.DOTALL);
    static final Pattern COMMENT2 = Pattern.compile("//.*");
    
    public static void main(String[] args) throws IOException {
        for (String name : args) {
            BufferedReader r = new BufferedReader(new FileReader(name));
            FileWriter w = new FileWriter(name + ".out");
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = r.readLine()) != null) {
                sb.append(line).append(System.getProperty("line.separator"));
            }
            String s1 = COMMENT1.matcher(sb).replaceAll("");
            String result = COMMENT2.matcher(s1).replaceAll("");
            w.write(result);
            w.close();
        }
    }
}

言語仕様的には PostScript では%以降がコメントになります。 ただしプリンタや処理系によってはコメント中の情報を用いて処理をする場合があります...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
%!PS

/Decomment { % (filename) Decomment -
    true exch
    (r) file
    {
        % outputflag file
        dup read
        not { exit } if
        dup 37 eq {
            3 -1 roll pop false 3 1 roll
        } if
        dup dup 10 eq exch 13 eq or {
            3 -1 roll pop true 3 1 roll
        } if
        2 index {
            ( ) dup 0 4 -1 roll put
            print
        } {
            pop
        } ifelse
    } loop
    pop pop
} bind def

%---- Test Code ----
(decomment.ps) Decomment

文字列リテラルに対応していませんでした。あわてると駄目ですね。


文字列リテラル対応版です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.util.regex.*;
import java.io.*;

public class Decomment {
    static final String LITERAL = "\"(?:\\\\.|[^\"])*\"";
    static final String COMMENT1 = "(?s:/\\*.*?\\*/)";
    static final String COMMENT2 = "//.*";
    static final Pattern DECOM_PAT = Pattern.compile("(" + LITERAL + ")|" + 
            COMMENT1 + "|" + COMMENT2);
    static final String LINE_SEPARATOR = System.getProperty("line.separator");
    
    public static void main(String[] args) throws IOException {
        for (String name : args) {
            BufferedReader r = new BufferedReader(new FileReader(name));
            FileWriter w = new FileWriter(name + ".out");
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = r.readLine()) != null) {
                sb.append(line).append(LINE_SEPARATOR);
            }
            String result = DECOM_PAT.matcher(sb).replaceAll("$1");
            w.write(result);
            w.close();
        }
    }
}

Squeak Smalltalk で。

与えられた Smalltalk コードを抽象構文木に落とし、それに含まれるコメントノードを削除してから文字列に戻して出力しました(実際は、抽象構文木の初回出力時にそこからコメントノードが削除される副作用を流用)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
| decomment |
decomment := [:sourceStr |
    | dummySel tree |
    dummySel := 'dummy '.
    tree := Parser new
        parse: (dummySel, sourceStr) readStream
        class: UndefinedObject
        noPattern: false
        context: nil
        notifying: nil
        ifFail: [^nil]. 
    (tree printString; printString) allButFirst: dummySel size].

^decomment value: '"comment1" ^ self "comment2"'   "=> '    ^ self' "

Brainf*ckで。入力された文字のうち、コメント(実行可能な8文字以外全部)を削除します。終了せず、ひたすら入力を待ち続けるので適当に止めてください。

Brainfuck Developerで実行する場合は複数行の入力ができませんが、[Extras] - [Set Input File ...] から入力ファイルを指定できます。すばらしいツールです。あとは言語が良けりゃぁ言うことなし。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
+[>
  ,                      # {1} = input char
  >+<                    # {2} = 1
  [->>+>+<<<]            # {3:4} = !{1}
  >>>
  >++++++[-<------->]<-  # {4} = {4} minus 43
  [                      # if {4} not 43 (plus)
    -[                   # if {4} not 44 (comma)
      -[                 # if {4} not 45 (minus)
        -[               # if {4} not 46 (dot)
          >++++[-<---->]<++
          [              # if {4} not 60 (lt)
            --[          # if {4} not 62 (gt)
              >+++++[-<------>]<+
              [          # if {4} not 91 (open)
                --[      # if {4} not 93 (close)
                  <<->>  #   then {2} = 0
                  [-]
                ]
              ]
            ]
          ]
        ]
      ]
    ]
  ]
  <<
  [>.<-]                 # if {2} then print {3}
  >[-]<<
<]

Scheme で書きました。 read で読み込んで write で書き出せばコメントを消すことができます。 但し、タブや改行まで消えてしまいます。 これ以外の方法で実装しようとするとパーサを書き直すことになり大変ですので、この方法に Pretty Printer を組み合わせるのが現実的な回答だと思います。
1
2
3
4
5
6
7
8
(define decomment (lambda ()
  (let loop ((s (read)))
    (if (not (eof-object? s))
      (begin (write s) (newline) (loop (read)))))))

(define main (lambda (args)
  (decomment)
  0))

修正版... 文字列中に%を書けたとは...

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
%!PS

/Decomment { % (filename) Decomment -
    true exch
    0 exch
    (r) file
    {
        % outputflag file
        dup read
        not { exit } if
        dup 40 eq {
            3 -1 roll 1 add 3 1 roll
        } if
        dup 41 eq {
            3 -1 roll 1 sub 3 1 roll
        } if
        dup 37 eq {
            2 index 0 eq {
                4 -1 roll pop false 4 1 roll
            } if
        } if
        dup dup 10 eq exch 13 eq or {
            4 -2 roll pop pop true 0 4 2 roll
        } if
        3 index {
            ( ) dup 0 4 -1 roll put
            print
        } {
            pop
        } ifelse 
    } loop
    pop pop pop
} bind def

%---- Test Code ----
(====%===) pop % ==
(decomment.ps) Decomment

doukaku.185.R:
------------------------------------------------------------------------
# Rのコメントは一行なので、大抵の場合はgrep("^[^#]", <文字列>, value=TRUE)で問題なさそうですが。

a <- 10  # 文頭以外のコメント
b <- "文字列中の#"

if(0){
    ここに書くコメントはRの文法上のコメントではないので削除されません
}


実行結果:
------------------------------------------------------------------------
> decomment("doukaku.185.R")
expression(a <- 10, b <- "文字列中の#", if(0){
    ここに書くコメントはRの文法上のコメントではないので削除されません
})
attr(,"srcfile")
doukaku.185.R
1
decomment <- parse

C++で書いてみました。
が、C++を書いた気にならない...

ちなみに、#if 0 ~ #endif は「コメント」ではないと思っているので対応していません。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include <iostream>
#include <fstream>

/* g++ -o decomment decomment.cpp */

// decomment
void decomment(
    std::ostream& os,
    std::istream& is
    )
{
  bool blc, olc; /* in block/one-line comment */
  bool str, chr; /* in string/char literal */

  blc=olc=str=chr=false;

  char c,pc='\0';
  while ( is.get(c) ) {
    // escape
    if ( c == '\\' ) {
      if ( !olc && !blc ) {
        if ( pc == '/' )
          os.put('/');
        os.put(c);
      }
      is.get(c);
      if ( !olc && !blc )
        os.put(c);
    }
    // end of one-line comment
    else if ( olc && (c == '\n' || c == '\r') ) {
      olc = false;
      os.put(c);
      pc = '\0';
      continue;
    }
    // end of block comment
    else if ( blc && pc == '*' && c == '/' ) {
      blc = false;
      pc = '\0';
      continue;
    }
    // start of one-line comment
    else if ( pc == '/' && c == '/' && !str && !chr && !blc ) {
      olc = true;
      pc = '\0';
      continue;
    }
    // start of block comment
    else if ( pc == '/' && c == '*' && !str && !chr && !olc ) {
      blc = true;
      pc = '\0';
    }
    // start/end of char literal
    else if ( !olc && !blc && c == '\'' ) {
      chr = !chr;
      if ( pc == '/' )
        os.put('/');
      os.put(c);
    }
    // start/end of string literal
    else if ( !olc && !blc && c == '\"' ) {
      str = !str;
      if ( pc == '/' )
        os.put('/');
      os.put(c);
    }
    else {
      if ( pc == '/' && !olc && !blc )
        os.put('/');
      if ( c != '/' && !olc && !blc )
        os.put(c);
    }
    pc = c;
  }
}

namespace {
  const char* test = "//\
                      " "/*" "hoge" "*/"
                      /* / * /* // */ "hige";
}
int main(int c, char** v)
{
  (void)test;
  if ( c < 2 ) {
    std::cout << "usage: " << v[0] << " <C++ source file>\n";
    return 0;
  }

  std::ifstream ifs(v[1]);
  if ( !ifs ) {
    std::cerr << "failed to open " << v[1] << "\n";
    return 1;
  }
  decomment(std::cout, ifs);
  return 0;
}

CPANモジュール PPI を利用して。
# SYNOPSISにまんまコメント削除の例が載ってたし
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
use strict;
use warnings;

use PPI;

=head1 NAME

decomment - strip comments/pods from code

=head1 SYNOPSIS

  decomment.pl <some.pl>

=head1 SEE ALSO

L<http://ja.doukaku.org/185/>

PPI L<http://search.cpan.org/~adamk/PPI-1.203/lib/PPI.pm>

=cut

if ( !$ARGV[0] ) {
  print "usage: $0 <some.pl>\n";
  exit 0;
}
# test data
my $testdata = "string with # :)"; # this is a comment;

# use PPI to strip comment from code
my $doc = PPI::Document->new($ARGV[0]);
$doc->prune(q/PPI::Token::Pod/);
$doc->prune(q/PPI::Token::Comment/);
print $doc->content;

__END__
this section is not a comment.

やっつけです

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>

int
main(int argc, char *argv[])
{
  FILE *f;
  char c;
  int flg = 0;

  if(argc != 2)
    exit(EXIT_FAILURE);

  f = fopen(argv[1],  "r");

  while((c = fgetc(f)) != EOF) {
    if((flg == 0) && (c == '/')) {
      flg = 1;
      continue;
    } else if((flg == 1) && (c == '/')) {
      flg = 2;
    } else if((flg == 1) && (c == '*')) {
      flg = 3;
    } else if((flg == 1) && (c != '/') && (c != '*')) {
      putchar('/');
      flg = 0;
    } else if((flg == 3) && (c == '*')) {
      flg = 4;
    } else if((flg == 4) && (c == '/')) {
      flg = 5;
    }

    if(flg == 0) {
      putchar(c);
    }
    if((flg == 2) && (c == '\n')){
      flg = 0;
    }
    if(flg == 5) {
      flg = 0;
    }
  }

  exit(EXIT_SUCCESS);
}

ストリームを使っているだけで実質Cですね…。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>

static void decomment(std::istream& in, std::ostream& out)
{
    in.unsetf(std::ios::skipws);

    char c;
    char prev = 0;

    while(in >> c)
    {
        if(c == '/')
        {
            prev = c;
            in >> c;
            if(c == '*')
            {
                do
                {
                    while((in >> c) && (c != '*'))
                    {
                        if(c == '\n')
                        {
                            out << c;
                        }
                    }
                } while((in >> c) && (c != '/'));

                c = ' ';
            }
            else if(c == '/')
            {
                while((in >> c) && (c != '\n'))
                    ; // NOP
            }
            else
            {
                out << prev;
            }
        }
        else if((c == '"') && (prev != '\\') && (prev != '\''))
        {
            do
            {
                out << c;
                if(c == '\\')
                {
                    in >> c;
                    out << c;
                }
            } while((in >> c) && (c != '"'));
        }

        out << c;
        prev = c;
    }
}

int main(int, char*[])
{
    decomment(std::cin, std::cout);

    return 0;
}

comment-kill がやるようです。

1
2
3
4
5
6
7
(defun decomment (buffer)
  "バッファ内のコメント文を削除します"
  (interactive "bdecomment ")
  (with-current-buffer buffer
    (save-excursion
      (goto-char (point-min))
      (comment-kill (point-max)))))

α置換の時に使ったパーサーがコメントを読み落としてしまうことを利用しました。もっと単純に文字列のパースでもできるはずですが、既存のソースに手を加えればできるということで…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module Main where

import Language.Haskell.Syntax
import Language.Haskell.Parser
import Language.Haskell.Pretty
import Data.Generics

pp :: ParseResult HsModule -> String
pp (ParseOk hsm) = prettyPrint hsm
pp _ = "parse failed"

main :: IO ()
main 
  = do mod <- getContents
       putStr $ pp $ parseModule mod

初投稿
泥です。
初めてre使った
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import re, sys

try :
    f = open(sys.argv[1], "r")
    raw_str = f.read()
    f.close()
except :
    print "file read error"

sharpe = re.compile(".*#.*")
s_quote = re.compile("'''")
d_quote = re.compile('"""')

de_sharpe_str = ""
tmp = raw_str.split("\n")
for i in tmp:
    de_sharpe_str += sharpe.split(i)[0] + "\n"

x = [s_quote.split(de_sharpe_str)[x]for x in range(0, len(s_quote.split(de_sharpe_str)), 2)]
y = [d_quote.split(de_sharpe_str)[x]for x in range(0, len(x), 2)]

print "\n".join(y)

これはパーサーの実装に依存したハックですね…


同じく,タブや改行は消えてしまいます.
writeだと,#0=(1 . #0#)で終わらないのでwirte/ssを使いました.
1
(port-for-each write/ss read)

すいません,訂正です.
#6573のでは,トップレベルに直接変数が書いてあった時に,くっ付いてしまいますね.
1
(port-for-each (lambda (s) (write/ss s) (newline)) read)

相互再帰風に書いてみました

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include <stdio.h>

typedef void* (*filter)(int c, FILE* out);

void drive(filter f, FILE* in, FILE* out)
{
    int c;

    do {
        c = fgetc(in);

        if (c == EOF)
            break;
    } while ((f = f(c, out)));
}

#define CALL(func)                              \
    do { return func; } while(0)

#define FINISH()                                \
    do { return NULL; } while(0)


void* normal_code(int, FILE*);
void* next_of_slash_in_normal_code(int, FILE*);
void* string(int, FILE*);
void* next_of_backslash_in_string(int, FILE*);
void* single_quote(int, FILE*);
void* next_of_backslash_in_single_quote(int, FILE*);
void* comment(int, FILE*);
void* next_of_star_in_comment(int, FILE*);
void* oneline_comment(int, FILE*);

void* normal_code(int c, FILE* out)
{
    switch (c) {
    case '/':
        CALL(next_of_slash_in_normal_code);

    case '"':
        fputc(c, out);
        CALL(string);

    case '\'':
        fputc(c, out);
        CALL(single_quote);

    default:
        fputc(c, out);
        CALL(normal_code);
    }
}

void* next_of_slash_in_normal_code(int c, FILE* out)
{
    switch (c) {
    case '*':
        CALL(comment);
        
    case '/':
        CALL(oneline_comment);

    default:
        fputc('/', out);
        fputc(c, out);
        CALL(normal_code);
    }
}

void* string(int c, FILE* out)
{
    fputc(c, out);

    switch (c) {
    case '\\':
        CALL(next_of_backslash_in_string);

    case '"':
        CALL(normal_code);

    default:
        CALL(string);
    }
}

void* next_of_backslash_in_string(int c, FILE* out)
{
    fputc(c, out);
    CALL(string);
}

void* single_quote(int c, FILE* out)
{
    fputc(c, out);

    switch (c) {
    case '\\':
        CALL(next_of_backslash_in_single_quote);

    case '\'':
        CALL(normal_code);

    default:
        CALL(single_quote);
    }
}

void* next_of_backslash_in_single_quote(int c, FILE* out)
{
    fputc(c, out);
    CALL(single_quote);
}

void* comment(int c, FILE* out)
{
    if (c == '*')
        CALL(next_of_star_in_comment);
    else
        CALL(comment);
}

void* next_of_star_in_comment(int c, FILE* out)
{
    if (c == '/')
        CALL(normal_code);
    else
        CALL(comment);
}

void* oneline_comment(int c, FILE* out)
{
    if (c == '\n')
        CALL(normal_code);
    else
        CALL(oneline_comment);
}

void decomment(FILE* in, FILE* out)
{
    drive(normal_code, in, out);
}

int main(int argc, char** argv)
{
    decomment(stdin, stdout);
    return 0;
}

おもいっきりベタな実装してみました。もしかすると抜けている部分があるかもしれません…。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
decomment :: String -> String
decomment s = decommentNormal s
  where
    decommentNormal         []                      = []
    decommentNormal         ('\'':'"':'\'':cs)      = '\'':'"':'\'':(decommentNormal cs)
    decommentNormal         ('\'':'\\':'"':'\'':cs) = '\'':'\\':'"':'\'':(decommentNormal cs)
    decommentNormal         ('"':cs)                = '"':(decommentQuote cs)
    decommentNormal         ('-':'-':cs)            = decommentCommentLine cs
    decommentNormal         ('{':'-':cs)            = decommentCommentBlock 0 cs
    decommentNormal         (c:cs)                  = c:(decommentNormal cs)
    decommentQuote          ('"':cs)                = '"':(decommentNormal cs)
    decommentQuote          ('\\':'"':cs)           = '\\':'"':(decommentQuote cs)
    decommentQuote          (c:cs)                  = c:(decommentQuote cs)
    decommentCommentLine    ('\n':cs)               = '\n':(decommentNormal cs)
    decommentCommentLine    (_:cs)                  = decommentCommentLine cs
    decommentCommentBlock 0 ('-':'}':cs)            = '\n':(decommentNormal cs)
    decommentCommentBlock n ('-':'}':cs)            = decommentCommentBlock (n-1) cs
    decommentCommentBlock n ('{':'-':cs)            = decommentCommentBlock (n+1) cs
    decommentCommentBlock n (c:cs)                  = decommentCommentBlock n     cs

main = getContents >>= putStrLn.decomment

ちょっと長いけど,Haskell, C, C++ 対応のつもり,

CommentStyleクラスとQuoteStyleクラスを使って,いろいろな言語に対応できるように工夫してみた.

  • commentLeadings メソッドは一行コメントの開始マーク(複数可)文字列を返すメソッド
  • commentOpenings メソッドはブロックコメントの開始マーク(複数可)文字列を返すメソッド
  • commentClosing メソッドは与えられたブロックコメントの開始マークに対応する修了マークを返すメソッド
  • commentNestable はブロックコメントがネスト可能かどうかを返すメソッド

など.

  • 文字列リテラル
  • ブロックコメントのネスト

に対応する.厳密にやるには,その言語のパーザを読んで結果の構文木データを,プリティプリンタ(自作することになる)に食わせるのかな.めんどうだけど...

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
{-# LANGUAGE EmptyDataDecls #-}
module Main (main) where

import Control.Arrow
import Data.Char
import Data.List
import Data.Maybe
import System.Environment

main :: IO ()
main = interact . genDecomment . listToMaybe . map (map toLower) =<< getArgs
   
genDecomment :: Maybe String -> String -> String
genDecomment (Just "c")    = decomment cstyle
genDecomment (Just "c++")  = decomment cppstyle
genDecomment _             = decomment hstyle

class CommentStyle c where
  commentLeadings  :: c -> [String]
  commentOpenings  :: c -> [String]
  commentClosing   :: c -> String -> String
  commentNestable  :: c -> Bool

class QuoteStyle q where
  quoteOpenings  :: q -> [Char]
  quoteClosing   :: q -> Char -> Char
  quoteEscape    :: q -> Char

splitWithPrefix :: Eq a => [a] -> [a] -> Maybe ([a],[a])
splitWithPrefix [] xs = Just ([],xs)
splitWithPrefix (p:ps) xxs@(x:xs)
  | p == x     = splitWithPrefix ps xs >>= return . ((x:) *** id)
  | otherwise  = Nothing

splitQuoted :: Eq a => a -> a -> [a] -> ([a], [a])
splitQuoted esc qm []   = ([],[])
splitQuoted esc qm (c:cs)
           | c == qm    = ([],cs)    
           | c == esc   = case cs of 
                            []     -> ([c],[])
                            c':cs' -> ((c:).(c':) *** id) $ splitQuoted esc qm cs'
           | otherwise  = ((c:) *** id) $ splitQuoted esc qm cs

decomment :: (CommentStyle s, QuoteStyle s) => s -> String -> String
decomment s ""         = ""
decomment s ccs@(c:cs) = case mapMaybe (flip splitWithPrefix ccs) $ commentLeadings s of
  (_,xs):_ -> decomment s $ snd $ break ('\n'==) xs
  []       -> case mapMaybe (flip splitWithPrefix ccs) $ commentOpenings s of
                (p,xs):_ -> decommentC s [p] xs
                []       -> if elem c (quoteOpenings s) then c:decommentQ s c cs 
                            else c