2011-06-22 3 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()方法

Eの呼びかけます$def,$valのように、$include以外の "コマンド"がさらに存在します。

これをパーサーコンビネータでどのように表現できますか?実際に、それは1と2の条件付きフォーク

ですが、私はこのようなものだ:uptoがScalate Scamelパーサから借りている

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

を(私はちょうど読み始めるとできませんかなり理解している)

$....コードラインと${...}ブロックを区別するためにnot('{')を使用しました。しかし、これは扱いにくく、他の「コマンド」にも及んでいません。

どうすればいいですか?

答えて

6

notの使用は冗長です。 |メソッドはを実装し、を選択します。 2番目のことは、最初のものが失敗した場合にのみ試みられます。

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

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

高速な解析とエラーメッセージのために、可能な限り頻繁にパーサーを「コミット」する必要があります。私はこれがあなたがnot('{')を使ったときに得ようとしていたものだと思います。

上記パーサは'{'、その後javaStuffを見ていないが続く'$'を見れば今、それは順序(includedefで残り4つ'$' -alternativesのそれぞれをバックトラックと考えるだろう、val 、最後にjavaDirective)、の前に戻ってhtmlDirectiveを試してみてください。失敗するとエラーメッセージが表示されます。しかし、'{'が表示された場合、他の代替案は成功しない可能性があることを知っています。同様に、'$'で始まる行は、htmlDirectiveになることはありません。

'{'のようなものは、バックトラックが発生しないようにしたいと考えています。 '{'パーサが失敗してバックトラックしたい場合は、そのトラックで停止させ、バックトラック原因のエラーをエラーとして直接ユーザーに伝播する必要があります。

これを行う方法は、commitです。このファンクション/コンビネータはパーサpに適用されたときには、がpから出てくるのを見て、本来は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の目的は、parse-alternativesのシーケンスからどの失敗をユーザーに伝播するかを決定することです。これは無視してください。 >>/flatMap/intoあなたには、いくつかのより多くの練習を得ているまで、それは時間だ、Daniel Sobral's explanationを読む最後に、私は|||を使用していたことがありません、あなたはおそらくどちらかではないでしょうハッピー解析を)

・ホープ、この。。!助けてください。

+1

本当に素敵な説明です! –

+0

@ DanielC.Sobralありがとう!それを改善するための提案はありますか? – Harrison