2009-06-02 7 views
5

私のプロジェクトでは、BitTorrentプロトコルの一部を実装しようとしていますが、これはhereです。具体的には、ソケットの転送用にデータを安全にエンコードするための「Bencoding」部分を使用したいと思います。形式は次のとおりです。特定の長さの文字列と正規表現を一致させる方法

8:a string => "a string" 
i1234e => 1234 
l1:a1:be => ['a', 'b'] 
d1:a1:b3:one3:twoe => {'a':'b', 'one':two} 

エンコード部分は簡単ですが、デコードはかなり面倒です。たとえば、文字列のリストがある場合、個々の文字列に分割する方法はありません。私はPyParsingとカスタムトークンパーサーを含むいくつかの異なるソリューションを試しました。私は現在、正規表現を使用しようとしていますが、それはかなりうまくいくようですが、私はまだ文字列の問題に悩まされています。私の現在の正規表現は次のとおりです。

(?P<length>\d+):(?P<contents>.{\1}) 

しかし、私は2番目のグループの長としての最初のグループを使用するように見えることはできません。これを行う良い方法はありますか?それとも私はこのすべてに間違っているのですか?答えは私の目の前に座っていますか?

+3

未解答の確認が、元のビットトレントクライアントは、オープンソースです。そしてそれはPythonでさえあります!だから、あなたは周りを突く試みることができます:http://bittorrent.cvs.sourceforge.net/viewvc/bittorrent/BitTorrent/ – MatrixFrog

+17

"そして今あなたは2つの問題があります! :: rimshot :: –

+0

リンクありがとう、MatrixFrog。私はそのファイルをインポートし、私のプログラムで元の実装を使用するつもりだと思います。 –

答えて

8

あなたがこのために使用するすべてのパーサは(すなわち、ものを覚えている)ステートフルする必要があるとしており、正規表現は、によっておよびステートフル、大規模ではありません。彼らはこの仕事のための間違ったツールです。

これらが唯一のデータ型であれば、最初の文字を読み込んだ後に適切なパーサーに制御を渡して、各データ型のカスタムパーサーを作成すると思います。

私は実際にこれを実装しましたが、それは遅いです。

さてさて、私は実装を書くことにしました:

from StringIO import StringIO 
import string 

inputs = ["10:a stringly", 
     "i1234e" , 
     "l1:a1:be", 
     "d1:a1:b3:one3:twoe"] 

# Constants 
DICT_TYPE = 'd' 
LIST_TYPE = 'l' 
INT_TYPE = 'i' 
TOKEN_EOF = '' 
TOKEN_END = 'e' 
COLON  = ':' 


class BadTypeIndicatorException(Exception):pass 


def read_int(stream): 

    s = "" 

    while True: 
     ch = stream.read(1) 
     if ch not in [TOKEN_EOF, TOKEN_END, COLON]: 
     s += ch 
     else: 
     break 

    return s 


def tokenize(stream): 

    s = "" 

    while True: 

     ch = stream.read(1) 

     if ch == TOKEN_END or ch == TOKEN_EOF: 
     return 

     if ch == COLON: 
     length = int(s) 
     yield stream.read(length) 
     s = "" 

     else: 
     s += ch 


def parse(stream): 

    TYPE = stream.read(1) 

    if TYPE in string.digits: 
     length = int(TYPE + read_int(stream)) 
     return stream.read(length) 

    elif TYPE is INT_TYPE: 
     return int(read_int(stream)) 

    elif TYPE is LIST_TYPE: 
     return list(tokenize(stream)) 

    elif TYPE is DICT_TYPE: 
     tokens = list(tokenize(stream)) 
     return dict(zip(tokens[0::2], tokens[1::2])) 

    else: 
     raise BadTypeIndicatorException 



for input in inputs: 
    stream = StringIO(input) 
    print parse(stream) 
+1

正規表現はステートフルです。正規表現と異なるパーサとの唯一の違いは、正規表現は固定された有限状態しか持たないことです。実際、それは通常の言語を定義する一般的な方法の1つです。固定された有限量の状態を使用して解析できる言語です。 –

+1

@Dietrich - あなたが言っていることを理解していますが、実際には "状態"という言葉の全く異なる2つの意味について話しています。現代のプログラミングの言葉は、私が使ったときによく使われます。つまり、いくつかのプロセスは操作間のことを覚えています。正規表現では、そのコンテキストを呼び出すことがあり、正規表現は文脈自由であるように大きく設計されています。 – Triptych

+0

私は答えとしてこれを選択しましたが、私はホイールを再発明しないことにしました。そのため、上記にリンクされたMatrixFrogのBitTorrent実装を使用しました。さもなければ、私はあなたの実装やそれに基づいて何かを使ったでしょう。 –

2

文字列を2回解析するとできます。最初の正規表現を適用して長さを取得します。 2番目の正規表現の長さを連結して、有効な式を作成します。

ことがpythonで行うことができますが、C#でのサンプルのようになりますかわからない:

string regex = "^[A-Za-z0-9_]{1," + length + "}$" 

は、長さが以前から決定されるalpanumericであるか、または_できる文字の長さnoに1を一致させるには正規表現は長さだけを取得します。

・ホープこのことができます:)

1

あなたは仕事のための間違ったツールを使用している...これは、状態維持のいくつかの並べ替えを必要とし、一般的に言えば、正規表現はステートレスです。私はPerlでbdecoding(およびbencoding)の


例実装がhereを見つけることができます。

その関数がどのように機能するかの説明(私はやったことがないので、それをコメントしてもらう[おっと]):

基本的にはあなたが何をする必要があるか、セットアップが再帰関数です。この関数は文字列参照を取るので(変更可能)、何かを返します(これは、配列、ハッシュテーブル、int、または文字列である可能性があることを意味します)。

機能自体は、単なる文字列の最初の文字をチェックし、そのことをベースとどのように処理するかを決定:

  • それはiであれば、Iと最初の間のすべてのテキストを解析しますeとなり、許可されているルールに従ってintとして解析してみます。
  • 数字の場合は、までの数字をすべて読み取って、その文字列から多くの文字を読み取ってください。最初の文字としてリットルまたはDがあれば物事が開始

リストと辞書されているが、あなたは現在を渡した後、l/dを取り除く必要があり、...面白い取得します文字列を関数に戻して、リストまたは辞書の要素の解析を開始できるようにします。次に、返された値をeにヒットし、残っている構造体を返すまで、適切な構造の適切な場所に格納します。

私が実装した機能は破壊的であることを覚えておいてください。渡された文字列は、関数が参照渡しにより返されたときに空であるか、より正確には解析されて返されたものがありません(これが再帰的に使用できる理由です:手つかず)。最初の呼び出しのほとんどの場合、これはあなたが何か奇妙なことをしていない限りすべてを処理するはずですので、上記のことが成り立ちます。

+0

Pythonの文字列は不変なので、少し違うようにする必要があります。 –

+0

おそらく、オフセット変数などを渡しているでしょうか?またはループでそれを行う。私の心は再帰的にほとんどの時間の作品です。 –

2

これは2つの手順で行います。正規表現は、実際には、このような単純な解析問題のために少し残忍です。ここで私はそれを行うだろう方法は次のとおりです。

def read_string(stream): 
    pos = stream.index(':') 
    length = int(stream[0:pos]) 
    string = stream[pos+1:pos+1+length] 
    return string, stream[pos+1+length:] 

それは、構文解析の機能的なスタイルの方法ですが、それが解析された値とストリームの残りの部分を返します。リストでは

、多分:

def read_list(stream): 
    stream = stream[1:] 
    result = [] 
    while stream[0] != 'e': 
     obj, stream = read_object(stream) 
     result.append(obj) 
    stream = stream[1:] 
    return result 

そしてあなたは、ストリームの最初の文字をチェックし、適切に派遣read_objectを定義したいです。

構文チェックなし
+0

任意の長さのストリーム上のSslice構文はおそらく良い考えではありません。 – Triptych

1

擬似コード、:

define read-integer (stream): 
    let number 0, sign 1: 
     if string-equal ('-', (c <- read-char (stream))): 
      sign <- -1 
      else: 
      number <- parse-integer (c) 
     while number? (c <- read-char (stream)): 
      number <- (number * 10) + parse-integer (c) 
     return sign * number 

define bdecode-string (stream): 
    let count read-integer (stream): 
     return read-n-chars (stream, count) 

define bdecode-integer (stream): 
    ignore read-char (stream) 
    return read-integer (stream) 

define bdecode-list (stream): 
    ignore read-char (stream) 
    let list []: 
     while not string-equal ('e', peek-char (stream)): 
      append (list, bdecode (stream)) 
     return list 

define bdecode-dictionary (stream): 
    let list bdecode-list stream: 
     return dictionarify (list) 

define bdecode (stream): 
    case peek-char (stream): 
     number? => bdecode-string (stream) 
     'i' => bdecode-integer (stream) 
     'l' => bdecode-list (stream) 
     'd' => bdecode-dictionary (stream) 
+0

なぜ誰かがこれを落としたのか分かりませんが、私はちょうどオリジナルのビットトレントが(リンクのためにMatrixFrogのおかげで)それをしているのかチェックしました、そしてこれはほぼ正確にエラーチェックとプラスの流れを扱います。 – Svante