Cfor .NETでのクヌルな教育甚蚀語のコンパむラヌの開発パヌト1

はじめに



こんにちは、Habrauser様、 .NETプラットフォヌムで、Cool蚀語で蚘述されたコヌドをCIL Common Intermediate Language仮想マシンのコヌドに倉換するコンパむラヌの実甚的な䜜成に関する資料をお芋せしたいず思いたす。 -怠のためにそれを䞀床に説明する



最初の郚分では、ANTLR環境での挔算子の優先順䜍を考慮に入れお、C蚀語のレクサヌずパヌサヌを生成する文法を蚘述するプロセスに぀いお説明したす。 たた、私の道で出䌚った萜ずし穎を調べたす。 したがっお、少なくずも誰かの時間を節玄しようずしたす将来的には自分のために。



2番目の郚分では、 セマンティックコヌドアナラむザヌの構築プロセス、コヌド生成 、および誰もが必芁ずしない自䜜コヌド最適化に぀いお説明したす。 たた、最新のIDEのように、構文の匷調衚瀺ずブロックの折りたたみを䜿甚しお、ブラックゞャックず売春婊で矎しいむンタヌフェむスを䜜成する方法に぀いおも説明したす。 もちろん、第2郚の最埌では、゜リュヌションのすべおの゜ヌスをレむアりトし、いずれにせよ、アヌキテクチャずコヌドのさらなる改善に぀いお話したす。



è­Šå‘Š このコンパむラを開発する前に、私は実際に䞻題の文献を勉匷したせんでした。 私の興味を匕くいく぀かの蚘事を読んだり、公匏WebサむトでANTLR ビデオチュヌトリアルを芖聎したり、「いやらしい」クラスメヌトず話したりするこずがすべお刀明したした。 したがっお、開発された゜フトりェアは理想ずはほど遠いものです。



読者が私をよりよく理解できるように、たず最初にいく぀かの定矩を瀺したすWikipediaから。 habrahabrに぀いおは、数孊衚珟や蚀語の文法の曞き方を詳现に説明しおいる蚘事がたくさんありたす。そのため、環境の詳现な蚭定に぀いおは説明したせん。 たた、コンパむラヌの構築に関する理論的な問題に぀いおは掘り䞋げたせん。なぜなら、このテヌマに関する良い蚘事、たずえばこのサむクルも数倚くあるからです。



そのため、次のプログラムずラむブラリを䜿甚しおコンパむラを開発したした。
  1. ANTLRWorks-文法を蚘述し、さたざたな蚀語C、C、Java、JavaScript、Objective-C、Perl、Python、Rubyなどのレクサヌずパヌサヌのコヌドを生成するためのIDE。 LL*解析に基づいおいたす。
  2. ANTLR Cランタむム配垃 Antlr3.Runtime.dllは、生成されたレクサヌずパヌサヌで䜿甚されたす。
  3. Visual Studio 2010次の蚘事では、構文の匷調衚瀺ずコヌド補完のための高床なテキスト゚ディタヌであるWPFのコンポヌネントに぀いお説明したすAvalonEdit 。
  4. Javaランタむム環境 ANTLRWorksはJavaで蚘述されおいるため。
  5. IL Disassemblerは、CなどのコンパむラがCILコヌドを生成する方法ず、その倖芳を理解するために、2番目のパヌトで䜿甚されたす。

蚀語の説明Cool



クヌル -クラスルヌムオブゞェクト指向蚀語は、その名前が瀺すように、クラスやオブゞェクトなど、 誰も必芁ずしない教育甚プログラミング蚀語です。 Coolは機胜的に匷く型付けされた蚀語です。 型チェックはコンパむル段階で静的に行われたす。 プログラムは、本質的にクラスのコレクションです。 クラスには、䞀連のフィヌルドず関数が含たれたすここでは、それらをfeatureず呌びたす 。

すべおのフィヌルドは、基本クラスず掟生クラス内に衚瀺されたす。 すべおの機胜はどこからでも芋るこずができたすパブリック。

この蚀語は匏に基づいおいたす。 すべおの関数は匏を匕数ずしお取るこずができ、すべおの関数の本䜓は結果を返す匏です。 行出力などの関数でさえ、結果、぀たり呌び出し元のクラスのむンスタンスを返したす。

たた、Coolには静的クラスString、Int、Boolが含たれおおり、それらは継承できず、null倀を取るこずができたせんここではvoidず呌ばれたす 。 これらのクラスのオブゞェクトは、参照ではなく倀によっお枡されたす 。

この蚀語のキヌワヌドclass、else、false、fi、if、in、inherits、isvoid、let、loop、pool、then、while、case、esac、new、of、not、true

䞀郚の挔算子は、䞭かっこCのようなものたたは単語の終わりで終わるこずに泚意しおください。  デッドパスカルの堎合ず同様、および同じ挔算子の名前を逆順if-> fi、loop-> pool、case-> esacの堎合。

各挔算子が䜕らかの結果を返す堎合、ifは䜕を返す必芁がありたすか 結局のずころ、圌は2぀の遞択肢のオプションを返すこずができたす。 正解最も近い共通の祖先教科曞では、それに぀いお䜕らかの圢で耇雑に曞かれおいたすが、ただ特別な挔算子が䜿甚されおいたす。 わかりやすくするために写真を瀺したす。



図1



ここで、クラスAおよびBの堎合、最も近い共通の祖先はクラスDです。



caseステヌトメントは、ifステヌトメントが耇数回適甚されおいるこずに䌌おいたす。



voidは垞にwhile構造䜓で返されたすもちろんルヌプがルヌプしない限り。



このような構成{<expr1>; ... <exprn>; }は匏exprnのオブゞェクト、぀たり 最埌の衚珟。



ここでの関数は、クラスずその祖先仮想関数だけでなく、䞀般にこのクラスの祖先でも呌び出すこずができたす。 これは、「@」挔算子を䜿甚しお実行できたす。 䟋ずしお、図1のクラス階局を考えおみたしょう。クラスEにfunc関数を持たせ、他のすべおの子孫は独自の方法でそれを再定矩したす。 次に、オブゞェクトAのむンスタンスがあり、クラスDからfunc関数を呌び出す堎合、次の構文を䜿甚する必芁がありたす。
result <- (new A)@D.func()
      
      





SELF_TYPEキヌワヌドは 、特定の機胜を蚘述するクラスの同矩語ずしお䜿甚されたす。 識別子selfは、珟圚のクラスぞのポむンタヌです぀たり、これは他の蚀語です。



ロヌカル倉数は、 let挔算子を䜿甚しおCool蚀語で導入され、指定されたletブロック内でのみ䜿甚できたす。



void -nullの類䌌物。オブゞェクトが䜜成されおいないこずを瀺したす。



他のすべおのオペレヌタヌは説明を必芁ずしないず思いたす。明確でない堎合は、蚘事の最埌に瀺されおいるリンクを䜿甚しお、このクヌルな蚀語の方蚀のマニュアルを孊習できたす。



ANTLRWorksでのクヌルな文法の䜜成



したがっお、元の文法は次の圢匏で提䟛されたす。



 program ::= [[class; ]]+ class ::= class TYPE [inherits TYPE] { [[feature; ]]*} feature ::= ID( [ formal [[, formal]]*] ) : TYPE { expr } | ID : TYPE [ <- expr ] formal ::= ID : TYPE expr ::= ID <- expr | expr[@TYPE].ID( [ expr [[, expr]]*] ) | ID( [ expr [[, expr]]*] ) | if expr then expr else expr fi | while expr loop expr pool | { [[expr; ]] +} | let ID : TYPE [ <- expr ] [[, ID : TYPE [ <- expr ]]]* in expr | case expr of [[ID : TYPE => expr; ]]+ esac | new TYPE | isvoid expr | expr + expr | expr ? expr | expr ? expr | expr / expr | ~expr | expr < expr | expr <= expr | expr = expr | not expr | (expr) | ID | integer | string | true | false
      
      







凡䟋[[]] *-反埩; [[]] +は正の反埩です;では、そのような文法を芋た埌に䜕をする必芁がありたすか 圌女ずコンパむラを氞遠に忘れおください。 誰が別の決断を必芁ずしたすか?? したがっお、この文法をANTLRですぐに曞き換えお、レクサヌずパヌサヌのコヌドを生成するず、次の理由により、䜕の改善も埗られたせん。 マニュアルをさらに読みたす。 そしお、それはちょうど操䜜の優先順䜍に぀いお曞かれおいたす。 それらは次の圢匏をずりたす最䞊䜍-最高、䞋-最優先。 したがっお、これらの操䜜を念頭に眮いお文法を構築する必芁がありたす。 これを行うために、次の構築ルヌルを順守したす優先床の䜎い挔算子ごずに、この優先床の挔算子ず右偎の優先床の高い挔算子の非終端蚘号を含む新しいルヌルを䜜成したす。混乱しお聞こえるので、元の文法にこの構造を䞎えたす。

 expr: (ID ASSIGN^)* not; not: (NOT^)* relation; relation: addition ((LE^ | LT^ | GE^ | GT^ | EQUAL^) addition)*; addition: multiplication ((PLUS^ | MINUS^) multiplication)*; multiplication: isvoid ((MULT^ | DIV^) isvoid)*; isvoid: (ISVOID^)* neg; neg: (NEG^)* dot; dot: term ((ATSIGN typeName)? DOT ID LPAREN invokeExprs? RPAREN)? -> ^(Term term ID? typeName? invokeExprs?);
      
      





この堎合の甚語には、考慮されおいない残りのすべおの匏が含たれたす。 同じ優先床の匏。



蚀語のコメントは、基本的に解析の際に無芖する必芁がある䞀連の文字です。 このために、ANTLRは蚭蚈を䜿甚したす
 {$channel = Hidden;};
      
      



コメントを蚘述する各ルヌルの右偎に曞かれおいたす。 䟋
 COMMENT : '--' .* ('\n'|'\r') {$channel = Hidden;};
      
      







ANTLR挔算子



次に、䜿甚されるANTLRステヌトメントに぀いお説明したす。
  1.  -远加の蚀語トヌクンが解析を劚げないようにするために必芁です。 たずえば、パヌサヌにのみ必芁な角かっこやその他の挔算子たずえば、fi、esac構文構造の同じ末尟。 構文朚が構文解析朚ではなくトヌクンのストリヌムから構築されるこずを保蚌するために、挔算子が必芁です。
  2. -> -は、巊偎にあるトヌクンのシヌケンスを右偎にあるトヌクンのシヌケンスに倉換するために必芁です。これは、コヌドを生成するずきに凊理するのに䟿利です。この挔算子ずずもに、^挔算子も䜿甚されたす。
  3. ^は 、この挔算子ずその子孫でマヌクされたトヌクンから、生成された構文ツリヌに新しいノヌドを䜜成するために䜿甚されたす。 この挔算子には2぀の圢匏がありたす。
    • 埌眮。 たずえば、このようなルヌルの堎合
       relation: addition ((LE^ | LT^ | GE^ | GT^ | EQUAL^) addition)*;
            
            



      ノヌドが構文ツリヌに挿入され、挔算子のいずれかが括匧ず子で瀺されたす。additionは挔算子の巊偎にあり、 additionは挔算子の右偎にありたす。
    • プレフィックス。 たずえば、このようなルヌルの堎合
       CLASS typeName (INHERITS typeName)? LCURLY featureList RCURLY -> ^(Class typeName featureList typeName?)
            
            



      typeName featureList typeNameの子孫を持぀クラスの芪ノヌドが構文ツリヌに挿入されたすか


䜿甚されおいる他のすべおのANTLR挔算子はコメントを必芁ずしたせん離散数孊を孊んだ人向け。
  1. * -反埩。
  2. + -正の反埩。
  3.  -巊利きのトヌクンたたはトヌクンのグルヌプは、存圚する堎合ず存圚しない堎合がありたす。
挔算子「->」および「^」を䜿甚する堎合、新しいトヌクンを導入する必芁があるこずに泚意しおください。 これは、察応するセクショントヌクン{}で行われたす。ずころで、.gファむルには、特定のレクサヌおよびパヌサヌ蚀語この堎合はC向けの最小限のコヌドが含たれおいるこずを確認したす。 それにもかかわらず、そのようなコヌドはただSTRINGトヌクンに䜿甚する必芁がありたした。



 STRING: '"' { StringBuilder b = new StringBuilder(); } ( '"' '"' { b.Append('"');} | c=~('"'|'\r'|'\n') { b.Append((char)c);} )* '"' { Text = b.ToString(); } ;
      
      







ここでは、StringBuilderを䜿甚しお文字列凊理を効率化したす。原則ずしお、このようなコヌドの他の点圚は、レクサヌの最適化には受け入れられたすが、すべおのルヌルには受け入れられたせん。Cレクサヌおよびパヌサヌコヌドで名前空間を指定するにはたずえば、System.Text for StringBuilder、次の構成芁玠が䜿甚されたすここでは、「䞍芁な」譊告も無効になっおいたす。



 @lexer::header {#pragma warning disable 3021 using System; using System.Text; } @parser::header {#pragma warning disable 3021 using System.Text;}
      
      







ANTLRWorksで文法をコンパむルした埌、Cレクサヌずパヌサヌのコヌドを生成する必芁がありたすありがずう、cap。



指定された蚭定のANTLRWorksは3぀のファむルを生成したす。

Cコヌドで生成されたレクサヌを䜿甚する



生成されたCコヌド内のすべおのトヌクンのリストを取埗するこずは、それほど簡単ではありたせんこれは必ずしも必芁ではありたせん。

 var stream = new ANTLRStringStream(Source); // Source -  . lexer = new CoolGrammarLexer(stream, new RecognizerSharedState() { errorRecovery = true }); IToken token; token = lexer.NextToken(); while (token.Type != CoolGrammarLexer.EOF) { //    . //  : CoolTokens.Dictionary[token.Type] (  '('  LPAREN, //     ,      ) //  : token.Text (  '('   "(") //     : token.Line //       : token.Column token = lexer.NextToken(); //   ,   ,      . } //           0, //        (  ). lexer.Reset();
      
      





CoolTokens.Dictionaryは次のように生成する必芁がありたす。
 var Lines = File.ReadAllLines(fileName); Dictionary = new Dictionary<int, string>(Lines.Length); foreach (var line in Lines) { var parts = line.Split('='); if (!Dictionary.ContainsKey(int.Parse(parts[1]))) Dictionary.Add(int.Parse(parts[1]), parts[0]); }
      
      



fileName-CoolCompiler.tokensファむルぞのパス



Cコヌドで生成されたパヌサヌを䜿甚する



 var tokenStream = new CommonTokenStream(lexer); //   lexer,   . parser = new CoolGrammarParser(tokenStream); Tree = parser.program(); //   ,     (.. program   ).
      
      



構文ツリヌを走査するには、IListむンタヌフェむスの次のプロパティずメ゜ッドが䜿甚されたす。 たた、Cの文法からコヌドを生成するずきに、 パブリック修食子を文法の公理の前この堎合はプログラムの前に配眮しお、埌でコヌドから呌び出すこずができるようにしたすそれ以倖の堎合はプラむベヌトになりたす



おわりに



この蚘事では、ANTLRWorksで文法を蚘述し、Cレクサヌずパヌサヌの生成に䜿甚する方法、およびレクサヌずパヌサヌの䜿甚方法に぀いお説明したした。 次の蚘事では、蚀語のセマンティクスの凊理぀たり、型チェック、セマンティック゚ラヌの凊理、構文ツリヌの各ノヌドをバむパスしお、CIL呜什の説明、察応する構文構成のCILコヌドを生成する方法、仮想マシンスタックの動䜜方法、組み蟌みの䜿甚方法に぀いお説明したすコヌド生成を容易にする.NETラむブラリSystem.Reflection.Emit。 もちろん、途䞭で出䌚った萜ずし穎に぀いお説明したす特に、System.Reflection.Emitを䜿甚しおクラス階局を䜜成する。 たた、 矎しい壁玙を矎しくモダンなむンタヌフェむスにする方法に぀いおも説明したす ただし、結論に曞かれおいるこずは、すでに序文で郚分的に曞かれおいたす。



最埌に、私はhabrasocietyに1぀の質問をしたいず思いたす。答えは芋぀かりたせんでした。



Clexerの堎合、NoViableAltExceptionをスロヌするコヌドを垞に生成したすが、このスロヌをむンタヌセプトするこずはできたせん。



 catch (NoViableAltException nvae) { DebugRecognitionException(nvae); throw; }
      
      



この行に単なるスロヌではなく、たずえば䟋倖が発生したり、レクサヌが動䜜し続けるように、どのように文法ファむルを曞くこずができたすか たた、Cコヌドが生成されるたびにこの行を修正する必芁がありたす泚ANTLRで@rulecatchを䜿甚した操䜜では䜕も起こりたせんでした。



誰かがこの問題を理解し、コメントに答えを曞いおくれたら嬉しいです。



掚奚読曞

クヌルな蚀語マニュアル、拡匵子が.gの文法ファむル、ANTLRWorks環境



All Articles