2017-12-22 7 views
2

私はCのようなソースコードを持っています。このソースコードのすべての文字列を抽出してリストに保存しようとしています。コメント。 このソースコード内の文字列には、任意の文字、空白、およびコメントを含めることができます。Pythonを使用してコメント内にないすべての文字列を見つける

例:

// this is an inline comment with the name "Alpha 1" 

string x = "Alpha 2"; 
/** this is a block comment with the string "Alpha 3" */ 
foo("Alpha 4"); 
string y = "Alpha /* */ 5 // with comments"; 

出力:

["Alpha 2", "Alpha 4", "Alpha /* */ 5 // with comments"] 

私は与えられた文字列内のコメントを持つことができるので、私は正規表現を使用することができないという問題点(有効である)、そしてもちろんのIインラインコメント内またはブロックコメント内に文字列を含めることができます。

は、私は、コード内のすべての文字列を取得するには、このメソッドを使用します。

re.findall(r'\"(.+?)\"', code) 

をしかし、それはまた、内部のコメントである文字列を私に与えます。

ヘルプ

+2

正規表現は、非正規言語を解析するためのひどい考えです。実際のレクサー/パーサーを使用することを検討しましたか? yacc、lex、flex、bison?はい、それを学ぶのはより高いオーバーヘッドですが、エッジケースを離れることなく、適度に複雑な文法を正しく解析する唯一の方法です。 – ShadowRanger

+1

'lookahead'または' lookbehind'アサーションを使う必要があるかもしれません。正規表現とのヒントについてはhttps://pythex.org/をご覧ください。 – Olexandr

+0

@ShadowRanger:コメントも文字列定数も入れ子になっていないので、言語は規則的です。私はそれを扱うために簡単なレクサー+明示的なFSMを使うことは、特にメモリ内のファイル全体を文字列としてはしたくない場合は、正規表現を使うよりもずっと簡単だということに同意します。 – 9000

答えて

1
re = r'(?:\/\/.+\n|\/\*.+\*\/)|(\".+\"|\'.+\')' 

これはほとんどの場合有効です。 // commentのようなコメントだけを改行で終わらせてください。コメントに含まれていないすべての単語は、キャプチャグループ1に返されます。ただし、コード内のすべてのコメントに対してNoneがあることに注意してください。

2

言語があなたが記述するのと同じくらい簡単な場合は、パーサを手書きで書くと思います。私はまだ入力をトークン化するために正規表現を使用します。

ここに行く:

考える
import re 
from itertools import takewhile 


def extract_strings(source): 
    def consume(it, end): 
     return list(takewhile(lambda x: x != end, it)) 
    tokens = iter(re.split(r'''("|/\*|\*/|//|\n)''', source)) 
    strings = [] 
    for token in tokens: 
     if token == '"': 
      strings.append(''.join(consume(tokens, '"'))) 
     elif token == '//': 
      consume(tokens, '\n') 
     elif token == '/*': 
      consume(tokens, '*/') 
    return strings 

data = ''' 
// this is an inline comment with the name "Alpha 1" 

string x = "Alpha 2"; 
/** this is a block comment with the string "Alpha 3" */ 
foo("Alpha 4"); 
string y = "Alpha /* */ 5 // with comments"; 
''' 
print(extract_strings(data)) 
1

>>> src='''\ 
... // this is an inline comment with the name "Alpha 1" 
... 
... string x = "Alpha 2"; 
... /** this is a block comment with the string "Alpha 3" */ 
... foo("Alpha 4"); 
... string y = "Alpha /* */ 5 // with comments";''' 

この正規表現は動作します:

>>> pat=re.compile(r"(?:\/\/.+$|/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)|(\"[^\"]*\"|\'[^']*\')", re.M) 
>>> [m.group(1) for m in pat.finditer(src) if m.group(1)] 
['"Alpha 2"', '"Alpha 4"', '"Alpha /* */ 5 // with comments"'] 

正規表現はhereを説明しています。

(しかし、パーサを使用すると、より堅牢である...)

0

パーサを書くことをテーマに、私は自分の使用してステートマシンを作成するには、この機会を利用:

State Machine for Extracting Strings

import sys 


ASTERISK = '*' 
DEFAULT = 'default' 
EOL = '\n' 
ESCAPE = '\\' 
QUOTE = '"' 
SLASH = '/' 


class ExtractStrings: 
    def __init__(self, multiline_string): 
     self.buffer = multiline_string 
     self.chars_collected = '' 
     self.strings = None 

    def noop(self, ch): 
     pass 

    def collect_char(self, ch): 
     self.chars_collected += ch 

    def return_string(self, ch): 
     self.strings.append(self.chars_collected) 
     self.chars_collected = '' 

    def parse(self): 
     self.strings = [] 
     state = { 
      'start': { 
       QUOTE: (self.noop, 'in_string'), 
       SLASH: (self.noop, 'first_slash'), 
       DEFAULT: (self.noop, 'start'), 
      }, 
      'in_string': { 
       QUOTE: (self.return_string, 'start'), 
       ESCAPE: (self.collect_char, 'escaping'), 
       DEFAULT: (self.collect_char, 'in_string'), 
      }, 
      'escaping': { 
       DEFAULT: (self.collect_char, 'in_string'), 
      }, 
      'first_slash': { 
       SLASH: (self.noop, 'line_comment'), 
       ASTERISK: (self.noop, 'block_comment'), 
       DEFAULT: (self.noop, 'start'), 
      }, 
      'line_comment': { 
       EOL: (self.noop, 'start'), 
       DEFAULT: (self.noop, 'line_comment'), 
      }, 
      'block_comment': { 
       ASTERISK: (self.noop, 'near_comment_block_end'), 
       DEFAULT: (self.noop, 'block_comment'), 
      }, 
      'near_comment_block_end': { 
       SLASH: (self.noop, 'start'), 
       ASTERISK: (self.noop, 'near_comment_block_end'), 
       DEFAULT: (self.noop, 'block_comment'), 
      } 

     } 

     current = 'start' 
     for ch in self.buffer: 
      default = state[current][DEFAULT] 
      action, next_state = state[current].get(ch, default) 
      action(ch) 
      current = next_state 

    def __iter__(self): 
     if self.strings is None: 
      self.parse() 

     return iter(self.strings) 

if __name__ == '__main__': 
    with open(sys.argv[1]) as f: 
     code = f.read() 

    for string_literal in ExtractStrings(code): 
     print('"%s"' % string_literal) 

どのように動作しますか?

ステートマシンは、さまざまなステート、各ステート(図には示されていません)で行うこと、および次のステートへの遷移を定義します。 ステートマシンが(ネストされた辞書として)定義されると、状態のアクションを実行し、次のcharを読み込み、ステートマシンを参照してどのステートに遷移すべきかを調べるだけです。

ステートマシンはネストされた辞書です。外部辞書の場合、キーは状態名であり、値は内部辞書である。内側の辞書の場合、キーは次の文字で、値は(アクション、次の状態)のタプルです。

関連する問題