2009-12-15 57 views
5

Pythonistas:關鍵字匹配:令牌的非貪婪咕嘟咕嘟

假設你想使用Pyparsing解析以下字符串:

'ABC_123_SPEED_X 123' 

ABC_123是一個標識符; SPEED_X是一個參數,而123是一個值。我想下面的BNF使用Pyparsing的:

Identifier = Word(alphanums + '_') 
Parameter = Keyword('SPEED_X') or Keyword('SPEED_Y') or Keyword('SPEED_Z') 
Value = # assume I already have an expression valid for any value 
Entry = Identifier + Literal('_') + Parameter + Value 
tokens = Entry.parseString('ABC_123_SPEED_X 123') 
#Error: pyparsing.ParseException: Expected "_" (at char 16), (line:1, col:17) 

如果我刪除從中間下劃線(並相應地調整Entry定義),它正確地解析。

我怎樣才能讓這個分析器是有點懶惰和等待,直到它的關鍵字匹配(相對於啜整個字符串作爲標識符和等待_,它不存在。

謝謝。

[注:這是我的問題完全重寫,我沒有意識到真正的問題是什麼]

+1

我知道這一點:你應該在'Parameter'的賦值中使用'|',而不是'''。 – 2009-12-15 06:56:40

+0

這個問題的標題應該是「pyparsing中的非貪婪匹配」。 – gotgenes 2009-12-15 07:48:42

+0

@gotgenes:完成。製作更清晰的標題。 – Escualo 2009-12-15 17:41:20

回答

7

我根據this one得出的答案,因爲你要做的是得到一個非貪婪的匹配。看起來這很難在pyparsing中發生,但並不是不可能的,有一些聰明和妥協​​。以下似乎工作:

from pyparsing import * 
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z') 
UndParam = Suppress('_') + Parameter 
Identifier = SkipTo(UndParam) 
Value = Word(nums) 
Entry = Identifier + UndParam + Value 

當我們運行這個從交互式解釋,我們可以看到以下內容:

>>> Entry.parseString('ABC_123_SPEED_X 123') 
(['ABC_123', 'SPEED_X', '123'], {}) 

注意,這是一個妥協;因爲我用SkipTo,Identifier可以充滿邪惡,令人厭惡的角色,不僅僅是漂亮的alphanums偶爾還有下劃線。

編輯:感謝保羅·麥圭爾,我們可以通過設置Identifier以下炮製一個真正優雅的解決方案:

Identifier = Combine(Word(alphanums) + 
     ZeroOrMore('_' + ~Parameter + Word(alphanums))) 

讓我們檢查是如何工作的。首先,忽略外層Combine;我們將在稍後討論。從Word(alphanums)開始,我們知道我們將獲得參考字符串'ABC_123_SPEED_X 123''ABC'部分。請注意,在這種情況下,我們不允許「單詞」包含下劃線。我們將其分別構建到邏輯中。

接下來,我們需要捕獲'_123'部分,而不吸取'_SPEED_X'。我們現在也跳過ZeroOrMore並稍後返回。我們以下劃線作爲Literal開頭,但我們可以使用'_'的快捷鍵,這將使我們獲得領先的下劃線,但不是全部的'_123'。假如我們將另一個Word(alphanums)捕獲其餘的,但這正是通過消耗所有剩餘的'_123_SPEED_X'而使我們陷入麻煩的原因。相反,我們會說:「只要下劃線後面是而不是,Parameter,就可以解析它,作爲我的Identifier的一部分我們聲稱在pyparsing術語中爲'_' + ~Parameter + Word(alphanums)因爲我們假設我們可以有任意數量的下劃線+ WordButNotParameter重複,我們總結認爲表達ZeroOrMore結構。(如果你總是期望至少下劃線+ WordButNotParameter在最初,你可以使用OneOrMore)。

最後,我們需要包裝最初的Word和特殊下劃線+字重複在一起,這樣就可以理解它們是連續的,而不是由空白分隔的,所以我們將整個表達式包裝在一個Combine結構中。這樣'ABC _123_SPEED_X'將引發一個解析錯誤,但'ABC_123_SPEED_X'將正確解析。

還請注意,我不得不將Keyword更改爲Literal,因爲前者的方式太微妙和快速的憤怒。我不信任Keyword,我也不能與他們匹配。

+0

這可能是工作......我想弄清楚爲什麼它的參數聲明爲Literals的集合,並且如果Parameter被聲明爲關鍵字的集合將無法工作。謝謝!這可能就是答案! – Escualo 2009-12-15 18:05:42

+1

它與下劃線在'Keyword'的默認'identChars'中有關。 http://crpppc19.epfl.ch/doc/python-pyparsing/htmldoc/pyparsing.pyparsing.Keyword-class.html如果你使用'Keyword('SPEED_X',identChars = alphanums)',你會得到匹配的。但我會堅持'Literal'。 – gotgenes 2009-12-15 18:54:05

+1

重新定義標識符爲'Combine(Word(alphanums)+ ZeroOrMore('_'+〜Parameter + Word(alphanums)))'',我認爲這會達到標記。 (順便說一句,我很高興看到更多pyparsing用戶在這些SO問題上投入!) – PaulMcG 2009-12-15 20:47:19

-1

這回答一個問題,你可能也問過自己:「什麼是真實世界申請reduce?):

>>> keys = ['CAT', 'DOG', 'HORSE', 'DEER', 'RHINOCEROS'] 
>>> p = reduce(lambda x, y: x | y, [Keyword(x) for x in keys]) 
>>> p 
{{{{"CAT" | "DOG"} | "HORSE"} | "DEER"} | "RHINOCEROS"} 

編輯:

這是一個很好的答案,原來的問題。我將不得不在新的工作。

進一步編輯:

我敢肯定你不能做你想要做的事。 pyparsing創建的解析器不會執行預見。因此,如果您告訴它匹配Word(alphanums + '_'),它將繼續匹配字符,直到找到不是字母,數字或下劃線的字符。

+0

儘管很慢(我有一個巨大的文件),Pyparsing能夠完成所需的任務。 – Escualo 2009-12-15 21:29:15

+0

Pyparsing有兩種風格的lookahead,NotAny類(縮寫使用'〜'運算符)用於負向查找,以及FollowedBy類(沒有運算符快捷方式 - 我們必須在某處繪製線,否則我們可能會編寫Perl)爲了自信的前瞻。但是,如果pyparsing在它的「下一個匹配」的語法中沒有隱含地展望未來,那麼你必須使用這些結構中的一個在你自己的代碼中進行編碼。 – PaulMcG 2009-12-16 00:06:47

+0

此外,可以通過以下幾種方式之一來完成幾個文字的鏈接:'或(map(關鍵字,「RED GRN BLUE」.split()))'或'oneOf(「RED GRN BLUE」)'。 oneOf在這裏實際上是首選,因爲它具有內置的智能,可以分辨單獨的「>」和「> =」的主角之間的區別。 – PaulMcG 2009-12-16 00:09:47

1

如果您確信該標識符從不以下劃線結束,您可以在定義強制執行:

from pyparsing import * 

my_string = 'ABC_123_SPEED_X 123' 

Identifier = Combine(Word(alphanums) + Literal('_') + Word(alphanums)) 
Parameter = Literal('SPEED_X') | Literal('SPEED_Y') | Literal('SPEED_Z') 
Value = Word(nums) 
Entry = Identifier + Literal('_').suppress() + Parameter + Value 
tokens = Entry.parseString(my_string) 

print tokens # prints: ['ABC_123', 'SPEED_X', '123'] 

如果不是這種情況,但如果標識符長度是固定的,你可以這樣定義標識:

Identifier = Word(alphanums + '_' , exact=7) 
+0

不幸的是,如果標識符有兩個以上的組件,這個選擇將不起作用。例如。它打破了my_string ='ABC_123_Hola_SPEED_X 123'。 – Escualo 2009-12-15 18:13:31

+0

非常接近,標識符只需要多個組件的容差,再加上負向前視'〜Parameter'以避免意外讀取參數作爲標識符的一部分。 – PaulMcG 2009-12-15 23:59:42

1

您也可以解析標識符和參數作爲一個令牌,並在解析動作分解:

from pyparsing import * 
import re 

def split_ident_and_param(tokens): 
    mo = re.match(r"^(.*?_.*?)_(.*?_.*?)$", tokens[0]) 
    return [mo.group(1), mo.group(2)] 

ident_and_param = Word(alphanums + "_").setParseAction(split_ident_and_param) 
value = Word(nums) 
entry = ident_and_param + value 

print entry.parseString("APC_123_SPEED_X 123") 

上述示例假定標識符和參數始終具有XXX_YYY格式(包含一個下劃線)。

如果不是這種情況,則需要調整split_ident_and_param()方法。

+0

+1用於使用分析操作。不幸的是,OP只發布了一種標識符,我們在後面的文章中學到,可能會有*多於兩個下劃線分離的組件。我懷疑它也有可能是一些標識符只有一個組件,所以解析器真的必須注意那些下劃線。這通常是解析問題的方式,示例數據通常是大量可能的和可能的輸入的非常小且特殊的子集。 – PaulMcG 2009-12-16 00:03:40