.NETの埋め蟌み蚀語、たたはEric Lippertの䞻匵

たえがき


匷迫芳念があなたの頭の䞭にしっかりず座っおいるので、䜕幎もの間䜕床も䜕床も戻っおきたす。 反察偎から問題にアプロヌチしたり、新しい知識を掻甚したり、最初からやり盎したりするなど、質問が完党に解決するたで続けたす。 私にずっお、修正蚀語はプログラミング蚀語になりたした。 あるプログラムが私の目に他のプログラムを䜜成できるずいう事実は、理解できないフラクタルの矎しさを授けたした。 そのようなプログラムを曞くこず自䜓は時間の問題でした。



2回目のコヌスの埌に初めお来たした。 コンパむラ、仮想マシン、およびそれだけの暙準ラむブラリ党䜓を䜜成するのに十分なCの知識があるず確信したした。 このアむデアぱレガントで、若々しいマキシマリズムのロマンスを吹き蟌んでいたが、代わりに2幎間の勀勉な仕事の結果は怪しいものだった。 仮想マシンは生呜の兆候を瀺し、 氞遠の同志が曞くのを助けた疑䌌アセンブラ䞊でかなり単玔なスクリプトを実行できたにもかかわらず 、プロゞェクトはすぐに䞭止されたした。 代わりに、.NETプラットフォヌム甚の蚀語を䜜成しお、無料のガベヌゞコレクション、jitコンパむラ、および巚倧なクラスラむブラリのすべおの機胜を取埗するこずにしたした。 コンパむラはわずか6か月で実装され、゜ヌスコヌドがCodePlexにアップロヌドされ、それで卒業蚌曞を守るこずができたした。

しかし、ただ䜕かが欠けおいたした。 すべおの利点のために、卒業蚌曞甚に開発された蚀語は、すべおの型ず関数の明瀺的な宣蚀を必芁ずし、䞀般的なサポヌトがなく、匿名関数を䜜成できず、実際にその適甚範囲は䞍明でした。 別の自転車を発明するずいう決定は、1幎埌、 Windows Phone向けのゲヌムの䜜成を終えお、次に䜕をすべきかを考え始めたずきでした。 次の芁件が新しい蚀語に蚭定されたした。


前述の氞遠に参加したいずいう願望を衚明し、仕事が沞隰し始めた。 圌は蚀語の蚭蚈の䜜成に積極的に参加し、Fでパヌサヌを䜜成したした。構文ツリヌず内郚むンフラストラクチャの説明を取り䞊げたした。

蚘事の埌半で、結果が䜕だったのか、途䞭で遭遇した萜ずし穎、そしお蚘事にこのような黄色の芋出しが付いおいる理由に぀いお説明したす。

誰が別の自転車を必芁ずしたすか


ほが3幎前、ハブに関するトピックの1぀で、䞖界䞭に2500を超えるプログラミング蚀語がただあるこずがわかりたした。 なぜ他の誰かが䟿利になるのでしょうか 他の人にはないかもしれないものは䜕ですか

JavaScriptずLuaの圧倒的な成功は、.NETホストアプリケヌションずの統合に重点を眮いお、蚀語を埋め蟌み可胜にする機䌚ずなりたした。 ここからプロゞェクトの名前-LENS - Language for Embeddable .NET Scriptingの略語が出たした。 「統合」ずは、スクリプトで型たたは関数を宣蚀する機胜、および実行時に倖郚プログラムず埋め蟌みプログラム間でオブゞェクトを盎接亀換するこずを意味したす。 たずえば、次のように

public void Run() { var source = "a = 1 + 2"; var a = 0; var compiler = new LensCompiler(); compiler.RegisterProperty("a", () => a, newA => a = newA); try { var fx = compiler.Compile(source); fx(); Console.WriteLine("Success: {0}", a); } catch (LensCompilerException ex) { Console.WriteLine("Error: {0}", ex.FullMessage); } } 

この䟋からわかるように、LENSサポヌトの接続は非垞に簡単です。アセンブリをプロゞェクトのReferenceに远加し、むンスタンスを䜜成しお゜ヌスコヌドをフィヌドするだけです。 すべおの「魔法」はRegisterPropertyメ゜ッドにありたす-その助けにより、ホストプログラムからの任意の倀が読み取りず曞き蟌みの䞡方でスクリプトで利甚可胜になりたす。 型ず関数には、それぞれRegisterTypeずRegisterFunctionメ゜ッドがありたす。

蚀語機胜


構文に関しおは、LENS蚀語はPythonおよびF蚀語から倚くのこずを孊びたした。 Cラむクな蚀語での10幎間の䜜業で、セミコロンず䞭括匧が痛いため、匏は改行で終わり、ブロックはむンデントされたす。

基本タむプ

基本的な型は、 bool 、 int 、 double 、 stringです。 これらのタむプの定数は、Cず同じ方法で蚘述されたす。

倉数宣蚀

倉数は、 varおよびletキヌワヌドを䜿甚しお宣蚀されたす。 最初は可倉倉数を宣蚀し、2番目は読み取り専甚倉数を宣蚀したす。

 let a = 42 var b = "hello world" 

制埡構造

条件はifブロックを䜿甚whileお曞き蟌たれ、ルヌプはwhileを䜿甚whileお曞き蟌たれたす。

 var a = 1 while(a < 10) if(a % 2 == 0) print "{0} is even" a else print "oops, {0} is odd" a a = a + 1 

制埡構造は倀を返したす 。 これは、割り圓お蚘号の右偎でも䜿甚できるif 

 let description = if(age < 21) "child" else "grown-up" 

機胜

䞊蚘の䟋からわかるように、 print関数は関数スタむルで呌び出されたす。最初に、関数たたはデリゲヌトオブゞェクトの名前、次にスペヌスで区切られた匕数が続きたす。 匕数ずしおリテラルたたは倉数名よりも耇雑な匏を枡す必芁がある堎合は、括匧で囲たれたす。

 print "test" print abc print "result is: " (1 + 2) 

パラメヌタなしで関数を呌び出すには、空の括匧のペアが䜿甚されたす。 実際のずころ、関数型パラダむムには「パラメヌタヌのない関数」などはありたせん。 Tru-functionariesは、 玔関数のみで動䜜するこずを奜み、匕数のない玔関数は本質的に定数です。 この堎合の空の括匧のペアは、 unit型のリテラル void同矩語であり、匕数がないこずを瀺したす。 同様に、パラメヌタヌのないコンストラクタヌが呌び出されたす。

関数宣蚀はキヌワヌドfun始たりfun 

 fun launch of bool max:int name:string -> var x = 0 while(x < max) println "{0}..." x x = x - 1 print "Rocket {0} name is launching!" name let rocket = new Rocket () rocket.Success countdown 10 

LENSにはreturnキヌワヌドはありたせん。 関数の戻り倀は最埌の匏です。 関数が䜕も返すべきではないが、最埌の匏が䜕らかのタむプである堎合、䜿い慣れたリテラル()たす。 キヌワヌドbreakおよびcontinueも提䟛されおいたせん。

珟圚䜜業䞭のバヌゞョンでは、関数を自動的にメモ可胜にするこずができたす。 これを行うには、関数の説明の前にpureキヌワヌドを䜿甚したす。 蚘憶に残る関数は、その倀を蟞曞にキャッシュしたす。そのようなパラメヌタヌのセットで関数が既に1回呌び出されおいる堎合、その倀はこの蟞曞から取埗され、再蚈算されたせん。

 pure fun add of int x:int y:int -> print "calculating..." x + y add 1 2 // output add 2 3 // output add 2 3 // no output! 

カスタム構造ず代数型

recordキヌワヌドを䜿甚しお、構造ずそのフィヌルドのリストを説明できたす。

 record Point X : int Y : int let zero = new Point () let one = new Point 1 1 

代数型は、 typeキヌワヌドず、この型が受け入れるこずができるオプションのリストによっお宣蚀されたす。 バリアントには、任意のタむプのラベルを付けるこずもできたす。

 type Card Ace King Queen Jack ValueCard of int let king = King let ten = ValueCard 10 print (ten is Card) // true 

構造の堎合、すべおのフィヌルドを䞀床に初期化するデフォルトのコンストラクタヌずコンストラクタヌが䜜成されたす。 たた、組み蟌み型の堎合、 EqualsおよびGetHashCodeメ゜ッドが自動的に䜜成されるため、それらを蟞曞のキヌずしお䜿甚できたす。

コンテナ

頻繁に䜿甚されるコンテナを初期化するために、特別なnew構文が䜿甚されたす

 let array = new [1; 2; 3; 4; 5] let list = new [[ "hello"; "world" ]] let tuple = new (13; 42.0; true; "test") let dict = new { "a" => 1; "b" => 2 } 

コンテナの堎合、最適なゞェネリックタむプが自動的に衚瀺されたす。 䟋

 let a = new [1; 2; 3.3] // double[] let b = new [King; Queen] // Card[] let c = new [1; true; "hello"] // object[] 

拡匵メ゜ッド

察応するフラグが蚭定で無効になっおいない堎合、コンパむラは適切な拡匵メ゜ッドも探したす。

 let a = Enumerable::Range 1 10 let sum = a.Product () 

LINQは、少し巧劙な構文を䜿甚しおサポヌトされおいたす。

 let oddSquareSum = Enumerable::Range 1 100 |> Where ((x:int) -> x % 2 == 0) |> Select ((x:int) -> x ** 2) |> Sum () 

さらに

コンパむラは、さらに倚くの興味深いものを実装しおいたす。


では、リッパヌはどうですか


これたでの蚘事を読んだ人の倚くは、確実に期埅を倱っおいたす-玄束されたドラマはどこですか 私は芚えおいたすが、芚えおいたすが、最初は䜙談です。

コンパむラのバック゚ンドは、すばらしい.NET Frameworkの䞀郚であるReflection.Emitラむブラリです。 型、メ゜ッド、フィヌルド、およびその他の゚ンティティをその堎で䜜成できたす 。メ゜ッドコヌドはMSILコマンドを䜿甚しお蚘述されたす 。 ただし、その幅広い機胜に加えお、かなりの厄介な萜ずし穎もありたす。

私が最初に遭遇した問題は、生成された型を怜査できないこずです。

 var intMethods = typeof(int).GetMethods(); //   var myType = ModuleBuilder.DefineType("MyType"); myType.DefineMethod("Test", MethodAttributes.Public); myType.GetMethods(); // NotSupportedException 

stackoverflowで、圌らは、䜜成されたメ゜ッドのリストを保存するこずず、それらを怜玢するこずをペンで行う必芁があるこずを明確に説明しおくれたした。 面倒ですが、難しくはありたせん。

しかし、さらに-もっず。

䜜成された型だけでなく、䜜成された型をパラメヌタヌずしお䜿甚する組み蟌みのゞェネリック型も怜査できないこずが刀明したした 以䞋に、Reflection.Emitで䜜成しようずするず問題が発生するクラスの䟋を瀺したす。

 class A { public List<A> Values = new List<A>(); } 

悪埪環が刀明しおいたす。 List<A>型のコンストラクタヌを取埗できるのは、アセンブリが既にファむナラむズされおおり、䞍芁になったずきだけです。

Stackoverflowに関する私の次の質問には、John Skeet Cin Depthの著者ずEric Lippert最近はC 開発者を率いるたで が回答したした。 ゚リックの評決は倱望し、取り消せたせんでした。

Reflection.Emitは匱すぎお、実際のコンパむラの構築には䜿甚できたせん 。 動的な呌び出しサむトやLINQク゚リでの匏ツリヌの発行など、小さなおもちゃのコンパむルタスクには最適ですが、コンパむラで盎面するさたざたな問題にはすぐにその胜力を超えおしたいたす。

Reflection.Emitは匱すぎお、実際のコンパむラを構築できたせん。 LINQク゚リでの動的呌び出しや匏ツリヌの䜜成など、「おもちゃ」のコンパむルタスクに適しおいたすが、実際のコンパむラの問題を解決するには、その機胜がすぐに十分ではなくなりたす。

Ericによるず、 Common Compiler Infrastructureを䜿甚しおコンパむラを曞き換えるのが最も正しいず思われたすが、このオプションに぀いおは考慮したせんでした。 頭に浮かんだ最初の決定は、蚀語から私たち自身の型を宣蚀する可胜性を排陀するこずでしたが、それはスポヌツマンらしくないでしょう。 本胜は、この制限を回避するための䜕らかの自明な方法がなければならないず瀺唆したした。

そしお、そのような方法は本圓にありたした それは私が予想したよりもはるかに明癜であるこずが刀明したした。

同じstackoverflowでプロンプトが衚瀺されたように、 TypeBuilderクラスには静的メ゜ッドがあり、次のようにメ゜ッド、フィヌルド、たたはプロパティを取埗できたす。

 var myType = createType("MyType"); var listType = typeof(List<>); var myList = listType.MakeGenericType(myType); var genericMethod = listType.GetMethod("Add"); var actualMethod = TypeBuilder.GetMethod(myList, genericMethod); 

ただし、ここには重倧な欠点がありたす。匕数の型は返されたメ゜ッドで眮換されたせん。 結果はList<MyType>.Add(T item)メ゜ッドのハンドルになりたす。匕数の型は、予想されるMyTypeではなく、正確にT 汎甚パラメヌタヌにMyTypeたす。

この欠点を解消するには、包含型ず基本メ゜ッドの蚘述から匕数型の倀を蚈算し、適切な堎所でそれらを眮き換えるアルゎリズムの実装が必芁でした。 TypeBuilderメ゜ッドず䞀緒にTypeBuilderこれらの2぀のメカニズムは悪埪環を回避したした。

結論-すばらしいものでさえ間違っおいる堎合がありたすが、Reflection.Emitでは完党に機胜するコンパむラを䜜成できたす 。 確かに、あなたはスチヌムバスを取る必芁がありたす。

Reflection.Emitの制限に぀いお詳しく知りたい人は、2009幎のMSDNブログ蚘事を読むこずをお勧めしたす。 生成できないクラストポロゞの䟋がいく぀かありたす。 泚意、VBの䟋

メモ化の驚異


メモ化のための蚀語サポヌトに割り蟌んで、私はこのプラクティスがコンパむラヌ自䜓の速床を改善するこずができるかどうか突然思いたした。 コンパむラで最も䞀般的に䜿甚されるものの1぀は、 TypeDistance関数です。 2぀のタむプ間の盞察的な継承たたは倉換距離を蚈算したす。これは次の堎合に必芁です。


この方法には12皮類以䞊のあらゆる皮類のチェックが含たれおおり、コンパむル時間のかなりの郚分を占めおいたした。 ただし、2぀のタむプ間の距離は時間ずずもに倉化しないため、 Dictionary<Tuple<Type, Type>, int>ディクショナリにキャッシュするこずは非垞に可胜です。 3぀の䞻芁なメ゜ッドをメモするのに玄30分かかり、いく぀かの耇雑なスクリプトのコンパむル時間を玄60倍短瞮したした 。

今埌のプロゞェクト


珟圚、コンパむラは安定しお動䜜しおおり、200以䞊のテストに合栌しおいたす。 実際のプロゞェクトですでに䜿甚できたすが、これは䜜業が完了したこずを意味するものではありたせん。 䞻なタスクは、パヌサヌをFからCに曞き換えるこずです。 FParsecラむブラリを䜿甚しおパヌサヌを構築するこず自䜓は正圓化されず、文法の倉曎をサポヌトするこずは耐えられなくなりたした。 さらに、すべおのFランタむムず500キロバむトの䟝存関係に沿っお、゚ラヌメッセヌゞずドラッグを衚瀺するためのかなり貧匱な機胜を提䟛したす。 すべおのコンパむラコヌドが250 kbを芁するこずを考えるず、これは非垞に倚くなりたす。

このため、䞀郚の機胜はコンパむラに既に実装されおいたすが、パヌサヌではただサポヌトされおいたせん-文法のわずかな倉曎により、雪厩のようなテスト倱敗の波が発生したす。 これらの「トリック」には、 for/foreach 、䟋倖凊理ず関数のメモ化䞭のfinallyセクション、およびわずかな構文の改良がありたす。

それ以倖の堎合、䜜業の前面はほが次のずおりです。


私たちは䞀緒にプロゞェクトに取り組んでいたすが、おそらく読者の䞭に志を同じくする読者がいるでしょう-そうすれば、仕事は速くなりたす。 より遠い蚈画には、Visual Studioでの蚀語サポヌトずデバッグシンボルの生成が含たれたす。

どこで詊すこずができたすか


すべおの゜ヌスコヌドは、githubリポゞトリで入手できたす。

github.com/impworks/lens

プロゞェクトには、コンパむラをテストできるテストホストプログラムが3぀ありたす。 圌らの仕事にはFRedistributableが必芁です。 Visual Studio 2010以前をむンストヌルしおいる堎合は、䜕もむンストヌルする必芁はありたせん。

Windows甚の収集されたデモ

コン゜ヌル

コンパむラヌの最も簡単なホスト。 プログラムは1行ず぀入力されるか、ファむルからロヌドされたす。 開始するには、行の末尟に#文字を入力したす。



プロッタヌ

y = f(x)圢匏の匏で2次元関数をプロットできたす。 範囲ずステップを蚭定できたす。


写真はクリック可胜です

グラフィックサンドボックス

最も機胜的なホストアプリケヌション。 スクリプトには、Circle型ずRect型が甚意されおおり、画面に衚瀺しお、それらの動䜜のロゞックを説明できたす。 いく぀かのデモスクリプトが含たれおいたす。



合蚈


それでも、プロゞェクトは実際的な問題を解決するこずよりも嚯楜のために行われたした。 もちろん、それは誰にずっおも圹に立たないかもしれたせんが、それに取り組むこずで玄8か月間の興味深い問題が生じ、フレヌムワヌクの内郚構造の耇雑さを研究するこずが可胜になりたした。 そしお、誰かが実際のプロゞェクトで重宝するなら、教えおください

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


All Articles