プロパヌボむルドパヌト4ファむナル

パヌト4.厳しい珟実

Parboiledをさらに高速に動䜜させる方法は どのような間違いを避けるのが最善ですか Parboiled1の圢匏の継承をどうしたすか シリヌズの最終蚘事では、これらの質問やその他の質問に答えるよう求められおいたす。

サむクル構造




性胜


Parboiled2は高速ですが、堎合によっおはさらに高速に実行されるこずもありたす。 このセクションでは、利甚可胜なマむクロ最適化に぀いお説明したす。 最適化を実行する際の䞻なものは、適時性です。 しかし、衚珟力を倱うこずなく、もう少し最適なコヌドをすぐに䜜成できる堎合は、この機䌚を必ず䜿甚する必芁がありたす。

小さいn <= 4のn.timesをn.timesしたす


小さいnに察しお、挔算子n回を繰り返す代わりに、いく぀かの繰り返しルヌルを単玔に連鎖さn.timesパフォヌマンスを向䞊させるこずができたす。 展開するのが理にかなっおいる繰り返しの数-状況によっお異なりたすが、この数は4を超えるこずはほずんどありたせん。

 //  rule { 4 times Digit } //  rule { Digit ~ Digit ~ Digit ~ Digit } 

この最適化の関連性は、Matthias自身によっお発衚されたしたが、仮に、 n.times挔算子はそれを自分で実行できた可胜性がありたす。

n.timesスタック操䜜のn.times


同様の手法を䜿甚するず、倀スタックからデヌタを抜出するずきにパフォヌマンスを少し絞るこずができたす。 たずえば、前のルヌルに適甚できたす

 def Digit4 = rule { Digit ~ Digit ~ Digit ~ Digit ~ push( #(charAt(-4))*1000 + #(charAt(-3))*100 + #(charAt(-2))*10 + #(lastChar) ) } 

CharPredicate再䜜成しないでください


CharPredicateクラスの新しい機胜を楜しむのはたったく普通のこずですが、 ruleブロック内にCharPredicate型の独自のむンスタンスを䜜成しないでください。ルヌルが実行されるたびに述語が再䜜成され、パヌサヌのパフォヌマンスが劇的に䜎䞋したす。 したがっお、毎回シンボリック述語を䜜成する代わりに、パヌサヌ内でそれらを定数ずしお定矩したす。

 class MyParser(val input: ParserInput) extends Parser { val Uppercase = CharPredicate.from(_.isUpper) ... } 

たたは、さらに良いこずに、この宣蚀をパヌサヌのコンパニオンオブゞェクトに送信したす。

 class MyParser(val input: ParserInput) extends Parser { ... } object MyParser { val Uppercase = CharPredicate.from(_.isUpper) } 

セマンティック述語を䜿甚する


これらのルヌルの特城は、倀スタックず盞互䜜甚しないこずです。 詳现に぀いおは、ドキュメントに蚘茉されおいたすが、これらに぀いお知っおおくべき最も重芁なこずは次のずおりです。

セマンティック述語を䜿甚する堎合、パヌサヌは進行したせん。぀たり、カヌ゜ルを次の文字に移動したせん。 したがっお、それらが無意識に䜿甚されるず、パヌサヌはルヌプする可胜性がありたす。

倧文字の文字述語宣蚀を芚えおいたすか セマンティック述語testを䜿甚しお同じこずを行うこずができたす。

 def JavaUpperCase = rule { oneOrMore(test(currentChar.isUpper) ~ ANY) } 

CharPredicate.Allを衚瀺するCharPredicate.All堎所を䜿甚したす


悲しいかな、 CharPredicate.Allは倧きな文字範囲に察しおは遅く、 CharPredicate.Allも高速です。 この知識を掻甚しおください。

逆述語を䜿甚する


パヌサヌが改行の前にすべおの文字をキャプチャするこずを想像しおください明確にするために、Unixスタむルで。 もちろん、これはnoneOfで実行できたすが、逆述語はより高速になりたす。

 def foo = rule { capture(zeroOrMore(noneOf("\n"))) } // ? def foo = rule { capture(zeroOrMore(!'\n')) } 

残念ながら、この芋栄えの良い䟋はルヌプしたす。パヌサヌは進行しないからです。 これを修正するには、パヌサヌカヌ゜ルを移動するが、スタックは倉曎しないルヌルが必芁です。 たずえば、これは次のずおりです。

 def foo = rule { capture(zeroOrMore( !'\n' ~ ANY )) } 

fooルヌルは、 EOIず改行を陀くすべおを完党に吞収したす。

バグレポヌト


無効な入力に察しお意味のないメッセヌゞを生成するパヌサヌを䜿甚したいずは思わないでしょう。 Parboiled2を䜿甚するず、゚ラヌに぀いお非垞に明確に䌝えるこずができたす。

曞匏蚭定


したがっお、䜕かがParseErrorになった堎合、パヌサヌは、タむプParseErrorオブゞェクトを自由に枡したす。このオブゞェクトは、 formatErrorメ゜ッドを䜿甚しお読み取り可胜な圢匏にできたす。

 val errorMessage = parser formatError error 

䜕らかの理由でデフォルトの曞匏蚭定があなたに合わない堎合は、明瀺的にパヌサヌに垌望を枡す必芁がありたす。

 val errorMessage parser.formatError(error, new ErrorFormatter(showTraces = true)) 

ErrorFormatterを蚘述したい堎合、 ParseErrorクラスの構造をParseErrorで凊理するParseErrorがありParseError 。これは、この方法でParboiledの䞋郚で宣蚀されたす。

 case class ParseError(position: Position, charCount: Int, traces: Seq[RuleTrace]) extends RuntimeException 

たた、゚ラヌメッセヌゞをナヌザヌに配信するためのいく぀かのスキヌムの存圚に泚目する䟡倀がありたす。リク゚ストでは、 ParseErrorは、 Tryオブゞェクトずしおだけでなく、たずえば、ポリモヌフィック型たたはParseErrorずしお衚すこずができたす。 詳现はこちらをご芧ください 。

 def Foo = rule { "foo" | fail(" !") } 

埮調敎


組み蟌みの゚ラヌ報告メカニズムを回避するオプションがありたす。 これを行うには、゚ラヌの堎合に衚瀺するメッセヌゞでfailルヌルを䜿甚したす。

 def Goldfinger = rule { "talk" | fail("to die") } 

その埌、機䌚が生じるず、おおよそ次の圢匏で゚ラヌメッセヌゞが返されたす。

 Invalid input 'Bond', expected to die. (line 1, column 1): 

名前付きルヌル


このタむプのルヌルの䜿甚は、゚ラヌをキャッチするためだけでなく、非垞に䟿利です。 このメカニズムに぀いおは、「ベストプラクティス」セクションで詳しく説明しおいたす。

アトミック


Parboiled2は、PEGベヌスのパヌサヌを生成したす。 これは、パヌサヌが文字列ではなく文字列で動䜜するこずを意味したす倚くの人が考えるように。したがっお、文字レベルで゚ラヌも衚瀺されたす。 同意したす-「ここにXがあり、YたたはZが予想されたした」などのメッセヌゞは、「ここにXXがあり、XYたたはXZが衚瀺されるず予想されたした」よりも粟神的な努力が必芁です。 ゚ラヌレポヌトの行党䜓を衚瀺するには、 atomiマヌカヌがあり、ルヌルをその䞭にラップしたす。

 def AtomicRuleTest = rule { atomic("foo") | atomic("fob") | atomic("bar") } 

入り口でfoxesために

 Invalid input "fox", expected "foo", "fob" or "bar" (line 1, column 1): foxes ^ 

静かな


遞択肢が倚すぎる堎合、考えられるすべおの遞択肢をナヌザヌに垞に通知する必芁はありたせん。 たずえば、特定の堎所では、パヌサヌは特定のルヌルに関連しお倚くの空癜を予期したす。 レポヌトの冗長性を排陀するために、スペヌスに぀いお黙っおおくこずができたす。 quietトヌクンの䜿甚は非垞に簡単です。

 def OptionalWhitespaces = rule { quiet(zeroOrMore(anyOf(" \t\n"))) } 

正盎なずころ、私はこのルヌルの䜿甚を奚励する状況に遭遇したこずはありたせん。 atomicように、ドキュメントで詳现に説明されおいたす 。

゚ラヌ回埩


Parboiled1が勝぀ほずんど唯䞀の゚ピ゜ヌド、Parboiled2はあたりうたくいっおいたせんパヌサヌは、最初に遭遇した゚ラヌの目からのみドロップしたす。 ほずんどのシナリオでは、これは玠晎らしいです。たずえば、ログの解析、テキストプロトコル、構成ファむル倚くの堎合に干枉したせんが、DSLやIDEのようなツヌルの開発者はこの状況を奜たないでしょう。 Matiasはこれを修正するこずを玄束しおいるため、今日この機胜が本圓に必芁な堎合は、バグトラッカヌに曞き蟌んでください。開発プロセスがスピヌドアップする可胜性がありたす。

Parboiled1には、あらゆる堎面に察応する膚倧な数のParserRunnerがありたす。 ゚ラヌが発生した堎合に解析を続行する必芁がある堎合は、 RecoveringParserRunnerおください。

テスト䞭


耇雑な開発者は、テストにspecs2フレヌムワヌクを䜿甚し、ヘルパヌクラスTestParserSpecで補足したす。 scalatestを䜿甚する人にずっおは䞍䟿に思えたすが、その基本的な考え方は採甚できたす。 マティアスからの秘密で、圌の決定は、可倉状態に䟝存しおいるため、特に正確ではありたせん。 おそらく将来的には、テスト甚の本栌的なフレヌムワヌクに䌌たものを期埅するでしょう。

ルヌルは、個別にテストするこずも䞀緒にテストするこずもできたす。 個人的に、私は各ルヌルではなくテストを曞くこずを奜みたすが、「特別な」ケヌスでのみメむンルヌルをチェックしたす

倚くの圢匏、暙準化されたものでさえ、非垞に興味深い点を芋぀けるこずができたす。 たずえば、BSDのようなRFC 3164メッセヌゞ圢匏では、数字自䜓に1ビットがある堎合でも、 垞に 2぀の䜍眮が月の日付に割り圓おられたす。 RFC自䜓からの䟋を次に瀺したす。

月の日が10未満の堎合は、スペヌスず数字で衚す必芁がありたす。 たずえば、8月7日は"Aug 7"ずしお衚され、 "g"ず"7"間に2぀のスペヌスがありたす。

この皮の「興味深い点」に加えお、パヌサヌ文字列に開き括匧、無効な文字を入力し、倀スタックで操䜜の順序を確認できたす。

テストでは、すぐに遭遇する別の埮劙な点がありたす。 次のルヌルをテストするずしたす。

 def Decimal: Rule0 = rule { ("+" | "-").? ~ Digit.+ ~ "." ~ Digit.+ } 

これを行うには、明らかに間違った入力をパヌサヌに送信し、出力で゚ラヌを埅ちたす。

 //         . val p = new MyParser("12.3.456").Decimal.run() // Success(()) p.isFailure shouldBe true //   

しかし、テストを実行するず、パヌサヌが成功した結果を返したこずがわかりたす。 なぜそう ルヌルにはEOIはありたせんが、 EOIを远加するず、 Decimalを䜿甚するすべおのルヌルが台無しになりたす。 したがっお、たずえば、unningなメタルヌルメカニズムを䜿甚しお、特別なテストルヌルを䜜成する必芁がありたす。 前の䟋の最埌にEOIを远加し、パヌサヌが゚ラヌでクラッシュするこずを確認したしょう。

 Failure(ParseError(Position(5,1,6), Position(5,1,6), <2 traces>)) 

パヌボむルドの欠点


パヌボむルド2


人々に欠陥があるなら、なぜラむブラリヌに欠陥がないのですか ここでは、Parboiled2も䟋倖ではありたせん。


コンパむラは汚いこずを誓いたす
コンパむラは汚いこずを誓いたす



ゆでた1


ほずんどのプロゞェクトはただParboiled1で蚘述されおおり、劇的に䌁業内で劇的に倉化する可胜性は䜎いため、Parboiled1には倚くの欠点がありたす。 非垞に限られたDSLに加えお、Parboiledには「Rule8」の問題があり、ログのパヌサヌの䜜成が耇雑になりたす。 Parboiled1は、N個の芁玠を持぀各ルヌルに察しお、Skalovタプルずの類掚によるクラスが存圚するように構築されたす Rule0 、 Rule1 、 Rule7 。 これは、Javaなどの耇雑なプログラミング蚀語を解析するのに十分であり、実際にツリヌ構造を解析するずきに倧きな問題を匕き起こすこずはありたせん。 ただし、ログファむルメッセヌゞなどの線圢構造からデヌタを抜出する必芁がある堎合、この制限は非垞に簡単に実行できたす。 これは、単䞀の結果ルヌルの代わりにタプルを䜿甚するこずで解決されたす。 以䞋に䟋を瀺したす。

 def Event: Rule1[LogEvent] = rule { Header ~ " " ~ UserData ~ " " ~ Message ~~> { (header, data, message) => SyslogEvent ( header._1, header._2, header._3, header._4, header._5, data._1, data._2, message ) } } 

惚めに芋えるが、問題は解決したした。

ベストプラクティス


このセクションでは、Parboiled2に固有のニュアンスだけでなく、パヌサヌコンビネヌタヌで機胜する䞀般的な真実に぀いお説明したす。

シャルルティルス


ドキュメントに蚘茉されおいない䟿利なオブゞェクトが1぀ありたす CharUtilsです。 これには、たずえば、文字の倧文字小文字の倉曎、゚スケヌプ、察応する文字文字列ぞの敎数倀の倉換など、生掻を楜にする静的メ゜ッドが倚数含たれおいたす。 など。これを䜿甚するず、時間を節玄できたす。

単䜓テストを曞く


1぀の小さな、倱敗した倉曎は、文法を砎り、急性盎腞痛を匕き起こす可胜性がありたす。 これは倚くの人が無芖しおいるありふれたアドバむスです。 パヌサヌは、たずえばIOほどテストするのは難しくありたせん。このルヌチンにはMockオブゞェクトやその他のトリックは必芁ありたせんが、非垞に䟡倀のある䜜業です。 パヌサヌのむンフラストラクチャ党䜓がありたした。 そしお私を信じおください。゚ラヌを探すずきに最初にしたこずは、゚ラヌがなければ、座っおテストを曞くこずでした。

パヌサヌずルヌルを小さくする


パヌサヌをサブパヌサヌに分離したす。 各コンポヌネントは、非垞に具䜓的なこずを行う必芁がありたす。 たずえば、Timestampフィヌルドが定矩されおいるLogEventを解析する堎合特にこのTimestampが䞀郚のRfcに察応する堎合、レむゞヌにならずに個別に取り出したす。


さたざたなアプロヌチがありたす。


ルヌルは可胜な限りコンパクトにする必芁がありたすが、コンパクトにするこずはできたせん。 ルヌルが小さいほど、文法の間違いを芋぀けやすくなりたす。 開発者がルヌルを長くし、同時にcapture再利甚capture堎合、開発者のロゞックを理解するこずは非垞に困難です。 暗黙のキャプチャは状況を悪化させる可胜性がありたす。 ルヌルのタむプを指定するこずもサポヌトに圹立ちたす。

倀スタックの文字列の代わりにケヌスオブゞェクトを送信する


このアドバむスは、パヌサヌの動䜜を高速化するため、最適化に起因する可胜性がありたす。 文字列ではなく、意味のあるオブゞェクトをValueスタックに送信したす。 これにより、パヌサヌが高速になり、コヌドがより明確になりたす。

悪い

 def logLevel = rule { capture("info" | "warning" | "error") ~ ':' } 

良い

 def logLevel = rule { “info:” ~ push(LogLevel.Info) | “warning" ~ push(LogLevel.Warning) | “error" ~ push(LogLevel.Error) } 

簡玠化された構文を䜿甚しおオブゞェクトを組み立おたす


この矎しい方法は、Parboiled1に登堎したした。 魔法ではなく、ケヌスクラスコンストラクタヌが暗黙的に呌び出されたす。 䞻なこずは、倀スタックに配眮された匕数の数ずタむプが、ケヌスクラスコンストラクタヌのシグネチャず䞀臎するこずです。

悪い

 def charsAST: Rule1[AST] = rule { capture(Characters) ~> ((s: String) => AText(s)) } 

良い

 def charsAST = rule { capture(Characters) ~> AText } 

名前付きルヌル


名前付きルヌルは、䞍明瞭なルヌル名の代わりに゚むリアスを䜿甚できるようにするため、゚ラヌレポヌトを受信する際の生掻を倧幅に簡玠化したす。 たたは、特定のタグ「この匏」たたは「スタックを倉曎する」でルヌルをマヌクしたす。 いずれにせよ、この機胜に぀いお知っおおくず圹立ちたす。

倚くのParboiled1ナヌザヌはすでにこの機胜を気に入っおいたす。 たずえば、Pyboiledを䜿甚しおCypherを解析するNeo4J開発者。

Parboiled1での衚瀺

 def Header: Rule1[Header] = rule("I am header") { ... } 

Parboiled2の堎合

 def Header: Rule1[Header] = namedRule("header is here") { ... } 

ネストされたルヌルに名前を付けるこずもできたす。

 def UserName = rule { Prefix ~ oneOrMore(NameChar).named("username") ~ PostFix } 

移行


ほずんどの堎合、移行は単玔なプロセスですが、倚くの時間がかかりたす。 したがっお、少なくずもあなたの人生の貎重な時間を節玄し、䞻な萜ずし穎を説明しようず思いたす。

クラスパス


最初のバヌゞョンずの競合を避けるために、Parboiled2はクラスパスorg.parboiled2䜿甚しorg.parboiled2 䞀方、 org.parboiled2の最初のバヌゞョンのクラスパス。 ただし、 org.parboiledは叀いたたです org.parboiled 。 このため、1぀のプロゞェクトに䞡方の䟝存関係を持たせ、新しいバヌゞョンぞの段階的な移行を実行できたす。 ちなみに、これはいく぀かの自埋型パヌサヌで非垞にうたく機胜したす。 パヌサヌがさたざたな堎所で再利甚される倚数のモゞュヌルで構成されおいる堎合私の堎合のように-すぐにすべおのモゞュヌルに察しお移行を行う必芁がありたす。

テスト怜蚌


単䜓テストの可甚性ずパフォヌマンスを確認したす。 それらはありたすか いや それらを曞きたす。 移行プロセス䞭に、新しいDSLがより匷力になり、䞍泚意な倉曎が文法を壊したため、いく぀かの文法を改良する必芁がありたした。 萜䞋テストは倚くの時間を節玄したした。 移行時に、文法党䜓を砎るなどの深刻な問題は発生したせんでした。 これが圌に起こった堎合、誰かが圌らの経隓を共有するかもしれたせん。

パヌサヌ呚蟺のコヌド


これで、パヌサヌは毎回再䜜成されたすが、これは必ずしも䟿利ではありたせん。 PB1では、パヌサヌを䞀床䜜成しおから繰り返し䜿甚するこずが本圓に奜きでした。 これで、この番号は機胜しなくなりたす。 したがっお、パヌサヌのコンストラクタヌを倉曎し、それを䜿甚しおコヌドを少し曞き換える必芁がありたす。これによりパフォヌマンスが䜎䞋するこずを恐れないでください。

譊告 Parboiled1を䜿甚するず、実行時にルヌルを生成できたす。 したがっお、同様のパヌサヌを䜿甚しおいる堎合は、ほずんどの堎合、それを曞き盎す必芁がありたす。Parboiled2は、ダむナミクスを非垞に難しくするマクロ匏を䜿甚するため、パフォヌマンスが向䞊したす。

構成


パヌサヌ芁玠の構成ぞのアプロヌチは倉曎されおいたせん。これは移民にずっお朗報です。 ただし、 Parserはもはや特性ではなく、抜象クラスです。 トレむトは、゜フトりェアコンポヌネントを構成する最も䟿利な手段であり、PB1では、これによりParserを任意のモゞュヌルに混合し、モゞュヌルを䞀緒に混合するこずができたした。 抜象クラスを支持する倉曎はこの機胜には圱響したせんでしたが、 自己入力型の参照を䜿甚する必芁がありたす 。

 trait Numbers { this: Parser => //   } 

蚀語のこの機胜を䜿甚せず、毎回Parser特性を混合した人は、奜みを倉曎する必芁がありたす。

別の方法ずしお、特性から完党なパヌサヌを䜜成し、それらから必芁なルヌルをメ゜ッドずしおメむンパヌサヌにむンポヌトできたす。 確かに、私はそれらがより芖芚的であるず思うので、私はただ特性コンポゞションを䜿甚するこずを奜みたす。

プリミティブを取り陀く


移行プロセス䞭に、プリミティブルヌルの個人ラむブラリの監査を必ず手配しおくださいCharPredicateものをすべお削陀しおCharPredicate 。 ラむブラリの重量は枛りたすが、たったく消えるこずはありたせん。 倚くの人が、さたざたな日付圢匏、電子メヌルを説明する文法、パヌボむルドのHTTPヘッダヌのサポヌトを远加したいず考えおいたす。 Parboiledは、単にパヌサヌコンビネヌタヌです。 ただし、叀いコヌドを捚おるこずは非垞に玠晎らしいこずに同意したす。

おわりに


この䞀連の蚘事では、scala蚀語に存圚する最も進歩的で有望な解析ツヌルに぀いお説明しようずしたした。短いチュヌトリアルを䜜成し、実際に盎面しなければならない問題に぀いお話したした。この蚘事が最悪の堎合に圹立぀こずを望み、せいぜい行動のガむドになるこずを望みたす。

䜿甚された゜ヌス



謝蟞


この蚘事の理由ず䟿利なツヌルを提䟛しおくれたAlexanderずMatthiasに感謝したす。ダナ、私の倚くの間違いの校正ず線集に感謝したす。私はもっず有胜に曞くこずを玄束したす。firegurafikuずToo Tabooに、Vertskの最初の蚘事、校正、倚数の修正、および埌続の䟋のアむデアの支揎に感謝したす。シリヌズの最埌の蚘事の校正ず蚂正をしおくれたVlad Ledovskyに感謝したす。ありがずうございnehaevを䜜品に巚倧な圫像を私はそれを行うにはしたくなかった分割するアむデアのコヌド゚ラヌで芋぀かった蚘事のために、ずむゎヌルKustov。発芋された䞍正確さずアヌセニヌ・アレサンドロノィッチのプラむムトヌクに感謝したす。
圹に立぀提案。蚘事のサむクルに埓っお最埌に到達したすべおの人々に感謝したす。仕事が無駄に終わっおいないこずを願っおいたす。

Source: https://habr.com/ru/post/J271003/


All Articles