2009-06-02 37 views
5

对于我的一个项目,我试图实现BitTorrent协议的一小部分,该协议可以在here找到。具体来说,我想使用它的“本地编码”部分,这是一种安全编码数据以通过套接字传输的方法。格式如下:如何将一定长度的字符串与正则表达式匹配

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}) 

但是,我似乎无法使用第一组作为第二组的长度。有没有什么好方法可以做到这一点?或者我接近这一切都是错误的,答案坐在我面前?

+3

不知道答案,但原来的比特激流客户端是开源的。甚至在Python中也是如此!所以你可以试试看:http://bittorrent.cvs.sourceforge.net/viewvc/bittorrent/BitTorrent/ – MatrixFrog 2009-06-02 06:20:46

+17

“现在你有两个问题!” :: rimshot :: – 2009-06-02 14:23:04

+0

感谢您的链接,MatrixFrog。我认为我只是导入该文件,并在我的程序中使用原始实现。 – 2009-06-02 15:00:09

回答

8

您使用此Any解析器将需要有状态(即记住的东西),和正则表达式是由大,无状态的。他们是这份工作的错误工具。

如果这些是唯一需要担心的数据类型,我想我会为每种数据类型编写自定义分析器,并在读取第一个字符后将控制权传递给适当的分析器。

我现在确实实现了一个,但已经晚了。

好吧,我决定写一个实现:

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

Regexes是有状态的。正则表达式和不同解析器之间的唯一区别是正则表达式只有固定的有限状态。实际上,这是定义常规语言的一种常见方式:任何可以使用固定的有限数量的状态进行分析的语言。 – 2009-06-02 06:59:35

+1

@Dietrich - 我明白你在说什么,但是我们真的在谈论“状态”两个完全不同的含义。现代编程中的这个词最常用于我使用它 - 某些流程记住了操作之间的事情。在正则表达式中,我们可能会调用该上下文,而正则表达式的大小被设计为无上下文。 – Triptych 2009-06-02 14:16:41

2

如果你解析字符串两次,你可以这样做。应用第一个正则表达式来获得长度。在第二个正则表达式中连接长度以形成一个有效的表达式。

不知道如何能在Python可以做到,但在C#示例将是:

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

为了配合1个字符的长度没有可以是alpanumeric或_其中长度从先前确定只检索长度的正则表达式。

希望这有助于:)

1

您正在使用错误的工作工具。这需要某种状态保持的,而一般来说,正则表达式是无状态。


一个例子实施PERL,我没有bdecoding(和bencoding)可以发现here

的那个功能工作原理的解释(因为我从来没有得到评论它[哎呀]):

基本上你需要做的是建立一个递归函数。这个函数接受一个字符串引用(所以它可以被修改)并返回“something”(这意味着它可能是一个数组,一个散列表,一个int或者一个字符串)。

函数本身只是检查字符串中的第一个字符,并决定做的一个基于的是什么:

  • 如果它是一个i,然后解析出所有和第一之间的文本e,并尝试根据允许的规则将其解析为int。
  • 如果它是一个数字,然后读取所有数字最多,然后读取字符串中的许多字符。

列表和词典事情开始变得有趣了......如果有d作为第一个字符,那么你需要剥去l/d,然后通过电流串返回到函数中,以便它可以开始列表或字典中的解析元素。然后将返回的值存储在适当结构的适当位置,直到您点击一个,并返回您留下的结构。

请记住,我实施它的功能是破坏性的。当函数返回时,传入的字符串是空的,因为它是通过引用传递的,或者更确切地说,它将没有它解析和返回的任何东西(这就是为什么它可以递归使用:它不处理的任何东西都留下不变)。尽管在最初的调用中大多数情况下,除非你做了一些奇怪的事情,否则这应该会处理所有事情,所以上面的结论是成立

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。

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)