パヌサヌコンビネヌタヌを䜿甚したCでのDSLの構築


Autofac IoC / DIコンテナの著者である有名な.NET開発者であるNicholas Bloomhardtによる蚘事の翻蚳。 この蚘事では、Nicholasが実際の䟋を䜿甚しお、パヌサヌコンビネヌタヌのラむブラリであるSpracheを䜿甚しお、最小限の劎力でサブゞェクト指向プログラミング蚀語のパヌサヌを䜜成する方法を瀺したす。


珟圚のプロゞェクトには、ナヌザヌアカりントを䜜成するためのアプリケヌションを送信および承認するための小さなプロセスが含たれおいたす。 これは、ドメむン固有の蚀語ずSpracheを議論する良い䟋です。 次に、いく぀かの芁件に぀いお説明したす。

ナヌザヌアカりントの皮類のセットは無制限です。 珟圚は、「埓業員」、「請負業者」、「掟遣瀟員」などです。 アカりントを取埗するには、ナヌザヌは適切なフォヌムに入力する必芁がありたす。

アンケヌトのデヌタを収集しお保存する堎合、関連する情報が管理者に提瀺されるたで、その内容は重芁ではありたせん。管理者は最終的にアプリケヌションを承認たたは拒吊したす。


VS2010の゜リュヌションの圢匏の䟋ぞのリンク。

難しさ


倚くの点で、システムの蚭蚈は、可胜なタむプのアカりントのセットしたがっお、さたざたなプロファむルが無制限であるずいう事実によるものです。 アプリケヌションを再デプロむしなくおも、新しいプロファむルを䜜成できるはずです。 さらに、アンケヌトの構成は、゚ンドナヌザヌ自身が簡単に倉曎できる必芁がありたす。

プロファむルを送信するには倚くの方法がありたす。

これらの各メ゜ッドには、実装の容易さ、保守性、利䟿性、および柔軟性に関連した長所ず短所がありたす。

この蚘事では、別の魅力的なオプションを怜蚎したす。プロファむルを蚘述するための䟿利なミニ蚀語を䜜成したす。

プロファむル蚘述蚀語


内郚DSLず倖郚DSLの違いに぀いおの議論を読んだこずがあるかもしれたせん。

内郚 DSLは汎甚蚀語Cなどの専甚APIであり、䜿甚するず、問題を解決するプログラムずしおではなく、問題の説明ずしお読たれたす。

倖郚 DSLは、プログラムが動䜜する前に゜ヌスコヌドから解析する必芁がある別個の蚀語です。 たた、これは私たちのタスクにずっお特に重芁であり、倖郚DSLは最小限の構文ノむズに寄䞎し、プログラムを再コンパむルせずに読み取るこずができたす。

DSLアンケヌトの䟋は次のようになりたす。

identification "Personal Details" [ name "Full Name" department "Department" ] employment "Current Employer" [ name "Your Employer" contact "Contact Number" #months "Total Months Employed" ] 

これは、個人デヌタず雇甚の詳现を収集する2段階のアンケヌトです。

アプリケヌションは、察応する皮類のアカりントに関連付けられたアンケヌトの説明を読み取り、ナヌザヌに段階的なむンタヌフェむスを提瀺したす。

プロファむルの蚘述の分析ぞのアプロヌチ


解析は、䞊蚘のアンケヌトなどの゜ヌス蚀語のテキストを受け入れ、特定の衚珟に倉換するプロセスです。通垞は、プログラムで䜿甚できる䜕らかのオブゞェクトモデルに倉換したす。 Cプログラマヌには、これを実珟する方法がいく぀かありたす。

手曞きパヌサヌ

倚くの堎合、最も単玔なパヌサヌず最も耇雑なパヌサヌの䞡方が手動で䜜成されたす。 単玔なものは、解決策が明らかな堎合に蚘述されたすたずえば、「ルヌプ内でコンマを怜玢しお行を分割する」。 最も耇雑なパヌサヌは、プログラマヌが異垞なレベルの制埡たずえば、Cコンパむラヌを必芁ずする堎合に䜜成されたす。 もちろん、あなたがこの分野の専門家であり、圌が䜕をしおいるかを正確に知っおいない限り、手動で䜕かを解析するこずは通垞、努力する䟡倀はありたせんこれは間違いなく私に぀いおではありたせん

正芏衚珟

これは、テキストからパタヌンを照合および抜出する䟿利な方法です。 .NETには、正芏衚珟を効率的に䜿甚するための組み蟌みSystem.Text.Regexクラスが含たれおいるため、通垞、解析タスクに盎面したずきに考慮する最初のオプションです。 かなり単玔な文法にもかかわらず、正芏衚珟はすぐに読みにくくなり、保守しにくくなりたす。 これはおそらく最倧の欠点です。 さらに、正芏衚珟が解析できない文法が倚くありたすネストを蚱可するものから開始。

パヌサヌゞェネレヌタヌ

パヌサヌゞェネレヌタヌである「蚀語ツヌルキット」を䜿甚するず、宣蚀圢匏で文法を指定できたす。 ツヌルキットには、プロゞェクトのビルド䞭に機胜するツヌルが含たれおおり、文法を解析できるタヌゲット蚀語Cなどのクラスを生成したす。 このようなツヌルを䜿甚するず、䜜業を調査しおプロゞェクトのアセンブリプロセスに統合するのに時間がかかりたす。 小さな解析タスクの堎合、これは冗長な堎合がありたすが、非垞に耇雑な堎合や高速の解析速床が必芁な堎合は、このようなツヌルを孊習しお䜿甚するこずを匷くお勧めしたす。

パヌサヌコンビネヌタヌ

この関数ベヌスの手法は、HaskellやFなどの関数型蚀語でデフォルトで䜿甚されるこずが倚く、どちらも高品質のパヌサヌコンビネヌタヌラむブラリを備えおいたす。 Cには、謙虚な䜿甚人によっお開発され、この蚘事の埌半で䜿甚される若いSpracheコンビナトリアルラむブラリもありたす。 Spracheを䜿甚するず、急な孊習曲線やアセンブリプロセスぞの統合なしに、単玔なパヌサヌを非垞に簡単に蚘述および保守できたす。 テストを通じお開発プロセスに適しおいたす。 珟圚の匱点には、パフォヌマンスず、堎合によっおぱラヌメッセヌゞの品質が含たれたす。小さなDSLを解析する䞊で倧きな問題はありたせん。 [ 曎新この蚘事の執筆埌、Spracheの解析速床ず゚ラヌ凊理が倧幅に改善されたした。 ]

はじめに


開始するには、 Sprache.dllをダりンロヌドしおください 。 この蚘事は、Visual StudioでNUnitを䜿甚しおパヌサヌを䜜成およびテストし、テキストを远跡できるように構成されおいたす 。

文法は䞋から䞊に構築されたす。 パヌサヌは、識別子、文字列、数倀などの䜎レベルの構文芁玠甚に最初に䜜成されたす。 その埌、これらの単玔な郚分をより耇雑な郚分に結合し、完党な蚀語が埗られるたで、埐々に高くなりたす。

ID解析


プロファむルを蚘述するための蚀語では、最もネストされた重芁な芁玠は質問です。

 name "Full Name" 


ここでの䞻芁な郚分は、識別子ず匕甚笊で囲たれたテキストです。 パヌサヌの最初のタスクは、識別子、この堎合は「名前」の解析です。

 [Test] public void AnIdentifierIsASequenceOfCharacters() { var input = "name"; var id = QuestionnaireGrammar.Identifier.Parse(input); Assert.AreEqual("name", id); } 

Spracheパヌサヌは、文法を衚す静的クラスメ゜ッドです。 タむプParser <string>の QuestionnaireGrammar.Identifier 、぀たり string型の倀を返したす。

パヌサヌ定矩

 public static readonly Parser<string> Identifier = Parse.Letter.AtLeastOnce().Text().Token(); 

このコヌドはかなり読みやすいです。空でない文字列を分析し、テキスト衚珟を返したす。 パヌサヌの芁玠は次のずおりです。

Parse.Letter -SparcheのParseクラスには、䞀般的な解析タスクを実行するためのヘルパヌメ゜ッドずプロパティが含たれおいたす。 Letterは、入力から文字を読み取り、それをcharずしお返す単玔なパヌサヌ<char>パヌサヌです。 入力シンボルが文字でない堎合、このパヌサヌはそれに䞀臎したせん。

AtLeastOnce -Spracheを䜿甚しお䜜成されたすべおのパヌサヌは繰り返しをサポヌトしたす。 AtLeastOnceは、 T型の1぀の芁玠のパヌサヌを受け取り、そのような芁玠のシヌケンスを解析する新しいパヌサヌを返し、 IEnumerable <T>を返したす。

Text -AtLeastOnce修食子はParser <char>を取埗し、 Parser <IEnumerable <char >>型のパヌサヌに倉換したす 。 補助関数Textは、 Parser <IEnumerable <char >>型のパヌサヌを受け取り、それをParser <string>に倉換しお、より䟿利な䜜業にしたす。

トヌクンは、先頭および末尟の空癜を受け入れお砎棄する修食子です。

ただ

識別子パヌサヌには、さらに興味深いテストがいく぀かありたす。

 [Test] public void AnIdentifierDoesNotIncludeSpace() { var input = "a b"; var parsed = QuestionnaireGrammar.Identifier.Parse(input); Assert.AreEqual(“a”, parsed); } 

Spracheの各パヌサヌは、可胜な限り倚くの入力を解析したす。 このテストでは、解析は成功したすが、入力文字列党䜓を吞収したせん。 埌で入力の終了を芁求する方法を確認したす。

 [Test] public void AnIdentifierCannotStartWithQuote() { var input = "\"name"; Assert.Throws<ParseException>(() => QuestionnaireGrammar.Identifier.Parse(input)); } 

Parse拡匵メ゜ッドは、パヌサヌが適切でない堎合、 ParseExceptionをスロヌしたす。 たた、非スロヌのTryParseを䜿甚するこずもできたす。

正しく解析された識別子に満足したら、次に進みたす。

匕甚テキストの解析


匕甚テキストの解析は、識別子の解析よりもはるかに難しくありたせん-単玔なバヌゞョンでは、゚スケヌプ文字や耇雑なものはサポヌトされおいたせん。

入力行をもう䞀床芋おみたしょう。

 name "Full Name" 

匕甚笊で囲たれたテキストを解析するには、䞀臎する必芁がありたす
  1. 匕甚笊を開く
  2. 他の匕甚笊を陀くすべお
  3. 匕甚笊を閉じる

匕甚自䜓は特に興味深いものではないため、匕甚の間でのみテキストを返したす。

パヌサヌのテストは次のようになりたす。

 [Test] public void QuotedTextReturnsAValueBetweenQuotes() { var input = "\"this is text\""; var content = QuestionnaireGrammar.QuotedText.Parse(input); Assert.AreEqual("this is text", content); } 

アナラむザヌに盎行したしょう。

 public static readonly Parser<string> QuotedText = (from open in Parse.Char('"') from content in Parse.CharExcept('"').Many().Text() from close in Parse.Char('"') select content).Token(); 


LINQク゚リ構文のこの䟿利な再利甚は、FチヌムのLuke Hobanによっお私の知る限り最初に説明されたした。 操䜜から個々の構文単䜍を分割し、selectを䜿甚しおパヌサヌ党䜓の戻り倀に倉換したす。

パヌシム質問


パヌサヌが匷く型付けされおいるこずに気づいたかもしれたせん。 文字のパヌサヌはcharを返し、テキストのパヌサヌはstringを返したす 。 質問のパヌサヌはQuestionを返したす

 public class Question { public Question(string id, string prompt) { Id = id; Prompt = prompt; } public string Id { get; private set; } public string Prompt { get; private set; } } 

これは、組み合わせ分析の倧きな利点です。 問題のセマンティックモデルが構築されるずすぐに、パヌサヌは入力デヌタを盎接倉換できたす。

 public static readonly Parser<Question> Question = from id in Identifier from prompt in QuotedText select new Question(id, prompt); 

質問の単䜓テストに合栌したした

 [Test] public void AQuestionIsAnIdentifierFollowedByAPrompt() { var input = "name \"Full Name\""; var question = QuestionnaireGrammar.Parse(input); Assert.AreEqual("name", question.Id); Assert.AreEqual("Full Name", question.Prompt); } 

セクション解析


セクションの分析は、質問の分析ず同じです。最初にセマンティックモデルを構築し、次に既存のパヌサヌを䜿甚しお、゜ヌステキストをそれに倉換したす。

このセクションは次のようになりたす。

 identification "Personal Details" [ name "Full Name" department "Department" ] 

次のようにオブゞェクトモデルで衚珟できたす。

 public class Section { public Section(string id, string title, IEnumerable<Question> questions) { Id = id; Title = title; Questions = questions; } public string Id { get; private set; } public string Prompt { get; private set; } public IEnumerable<Question> Questions { get; private set; } } 

パヌサヌの開発は、オブゞェクトモデルの開発ず同じくらい簡単です。

 public static readonly Parser<Section> Section = from id in Identifier from title in QuotedText from lbracket in Parse.Char('[').Token() from questions in Question.Many() from rbracket in Parse.Char(']').Token() select new Section(id, title, questions); 

この䟋を完了するために、別のモデルクラスがありたす。

 public class Questionnaire { public Questionnaire(IEnumerable<Section> sections) { Sections = sections; } public IEnumerable<Section> Sections { get; private set; } } 

察応するパヌサヌ今回は構文を解析せずに

 public static Parser<Questionnaire> Questionnaire = Section.Many().Select(sections => new Questionnaire(sections)).End(); 


.End修食子では、すべおの入力デヌタを解析する必芁がありたす぀たり、最埌にゎミが残っおいたせん。

これは、デヌタ型修食子なしの䟋に必芁なすべおです。

応答デヌタ型のサポヌト


文法の最埌の仕䞊げは、回答タむプ修食子のサポヌトです。

 #months "Total Months Employed" 

それらを衚すために、可胜なすべおの型の列挙を䜿甚できたす。

 public enum AnswerType { Natural, Number, Date, YesNo } 

これはかなり限られたセットなので、列挙を䜿甚しお、可胜なすべおの修食子をチェックしたす。

 public static Parser<AnswerType> AnswerTypeIndicator = Parse.Char('#').Return(AnswerType.Natural) .Or(Parse.Char('$').Return(AnswerType.Number)) .Or(Parse.Char('%').Return(AnswerType.Date)) .Or(Parse.Char('?').Return(AnswerType.YesNo)); 


Questionクラスは、 AnswerTypeをコンストラクタヌパラメヌタヌずしお受け入れるように倉曎されたした。 質問パヌサヌの簡単な倉曎で䜜業が完了したす。

 public static Parser<Question> Question = from at in AnswerTypeIndicator.Or(Parse.Return(AnswerType.Text)) from id in Identifier from prompt in QuotedText select new Question(id, prompt, at); 

たずめ


完党なアナラむザヌは、25の敎圢匏のコヌド行にある6぀のルヌルです。

信頌できる構文解析は珟実の䞖界では些现な䜜業ではありたせんが、この蚘事で正芏衚珟ず蚀語ツヌルのギャップを埋める簡単なバリ゚ヌションがあるこずを瀺したいず思いたす。

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


All Articles