2011-06-22 43 views
4

我試圖寫一個簡單的HTML模板引擎(爲了好玩),並想解析一個結構類似這樣的我如何做解析器組合條件檢查

A.正常線是HTML

B.如果符合$開始然後查看它作爲Java代碼線

$ if (isSuper) { 
    <span>Are you wearing red underwear?</span> 
$ } 

C.如果${}纏繞多條線路,在它所有的代碼應該是Java代碼。

D.如果符合$include開始那麼做就行了一些技巧(調用另一個模板)

$include anotherTemplate(id, name) 

這將創建的anotherTemplate一個新的實例,並調用它的render()方法

Ë 。除$include之外還會有更多的「命令」,比如$def,$val

如何在解析器組合器中表達這一點?實際上,這是一個有條件的叉子

爲1和2,我有這樣的事情:

'$' ~> ('{' ~> upto('}') <~ '}' | not('{') <~ newline) 

其中upto從Scalate的Scamel解析器借來的(我剛開始讀不能相當明白)

我用not('{')來區分$....代碼行與${...}塊。但這很麻煩,不會擴展到其他「命令」

所以我該如何做到這一點?

回答

6

您使用not是多餘的。 |方法實現訂購的選擇;第二件事情只有在第一件失敗的情況下才會嘗試。這應該是訣竅:

def directive: Parser[Directive] = 
    ('$' ~> 
    ('{' ~> javaStuff <~ '}' 
    | "include" ~> includeDirective 
    | "def"  ~> defDirective 
    | "val"  ~> valDirective 
    | javaDirective 
    ) 
    | htmlDirective 
) 

def templateFile: Parser[List[Directive]] = (directive <~ '\n').* 

爲了更快的解析和更好的錯誤消息,你應該儘可能「提交」你的解析器。我認爲這是你在使用not('{')時試圖得到的結果。

眼下,如果上述解析器看到一個'$'後跟一個'{'然後看到javaStuff,它會走回頭路,考慮四個剩餘'$' -alternatives,以便(includedefval ,最後javaDirective),然後回到'$'之前,嘗試htmlDirective,然後發出令人困惑的錯誤消息。但是如果我們看到一個'{',我們知道其他替代方案都不可能成功,那麼我們爲什麼要檢查它們呢?同樣,以'$'開頭的行不能是htmlDirective

我們希望諸如'{'之類的東西成爲無回溯點;如果解析器解析失敗並想要回溯,我們應該將其停止並將導致回溯的錯誤直接傳播給用戶,作爲錯誤。

要做到這一點的方法是commit。當應用於解析器p時,該函數/組合器查看p中出現的ParseResult,並且如果它最初是Failure(回溯信號),則將其更改爲Error(放棄完全信號),使其不變除此以外。通過適當的使用commit,在directive解析器變爲:

def directive: Parser[Directive] = 
    ('$' ~> commit('{' ~> commit(javaStuff <~ '}') 
       | "include" ~> commit(includeDirective) 
       | "def"  ~> commit(defDirective) 
       | "val"  ~> commit(valDirective 
       | javaDirective 
       ) 
    | htmlDirective 
) 

當我第一次學會了用解析庫,我發現它真的有用看the source code for Parsers;它使這些東西更清晰一些。 (一些其他技巧:appendParseResult#append的目的是決定哪一個解析替代序列中的哪個失敗應該傳播給用戶,現在只需忽略這些失敗。此外,我也不會太擔心>>/flatMap/into直到你得到一些更多的實踐;當它的​​時候,讀Daniel Sobral's explanation最後,我從來沒有使用|||,你可能也不會有的快樂解析)

希望這。!幫助。

+1

真的很好的解釋! –

+0

@ DanielC.Sobral謝謝!你有任何改進它的建議嗎? – Harrison

相關問題