.NETのBrainfuckコンパイラ

こんにちは。
BrainFuckの1週間があることがわかりました。特に、 この記事へのコメントで動的メソッドについて詳しく説明するように頼まれたので、コンパイラを書くことにしました。 この記事では、このコードのコンパイル方法を検討し、最も通常の単純な言語のコンパイラーを作成することを試みます。

エントリー


.NETプラットフォームに任意のソースコードをコンパイルするためには3つの事を行う必要があります。
  1. 新しいプロジェクトを作成し、System.ReflectionおよびSystem.Reflection.Emit名前空間をそれに接続します
  2. ソースコードの入力と検証を提供する
  3. このコードを実行可能ファイルにコンパイルします

そして、最初のもので問題がなければ、チェックとコンパイルでいくつかの問題が発生する可能性があります。

だから、ソースコードをチェックする


正しさのソースコードをチェックするために、我々はそれがオペランドで構成されて何を知っている必要があります。 それらの8つだけがあります。
  1. >-次のセルに移動
  2. <-前のセルに移動
  3. +-現在のセルの値を1増やす
  4. --現在のセルの値を1減らす
  5. 。 -現在のセルの値を出力します
  6. 、-外部から値を入力し、現在のセルに保存する
  7. [-現在のセルの値がゼロの場合、プログラムテキストで対応するセルの次のセルに進みます](ネストを考慮して)
  8. ]-現在のセルの値がゼロでない場合は、プログラムテキストで記号[(ネストを考慮に入れて)に戻る


全体として、ソースコードが正しいためには、ソースコードが空ではなく、ループの入れ子が壊れていないことを確認する必要があります。

コードをチェックする関数を書きましょう:

public bool CheckSource()
{
if (Src.Length == 0) throw new ArgumentException(" ");
int State = 0;
for (int i = 0; i < Src.Length; i++)
{
if (Src[i] == '[') State++;
if (Src[i] == ']') State--;
// , .
if (State < 0) throw new ArgumentException(String.Format(" . : {0}", i++));
}
if (State != 0) Console.WriteLine(" .");
return State == 0;
}


この関数は、コードが存在することと、開き括弧と閉じ括弧が正しい順序であり、それらの番号が同じであることを確認するだけです。

編集


30,000バイトのメモリ制限を持つコンパイラを実装することをすぐに予約してください。 各セルは、1バイトのサイズを有しています。

コンパイラを頭の中コンパイルするにはAssemblyBuilderクラスを理解する必要があります。
それに対する操作の結果は、実行可能ファイルまたはライブラリになります。
AssemblyBuilder ASM = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("BrainFuck Compiled Program"), AssemblyBuilderAccess.RunAndSave); //
ASM.Save(Filename);

このコードは空のアセンブリを作成しません 。 このアセンブリが空にならないようにするには、モジュールで満たす必要があります。
ModuleBuilder MDB = ASM.DefineDynamicModule(Filename); //

このコードは、アセンブリの名前に名前のモジュールを作成します。
そして、その中にクラスProgramを作成します
TypeBuilder TPB = MDB.DefineType("Program", TypeAttributes.Class); //
TPB.CreateType(); //

これでクラスができました。 その中ですべての操作が実行されます。
配列が必要です-メモリとその中のポインタ。
FieldBuilder FDB_1 = TPB.DefineField("Memory", typeof(byte[]), FieldAttributes.Private); // private byte[] Memory; // .
FieldBuilder FDB_2 = TPB.DefineField("Point", typeof(int), FieldAttributes.Private); //private int Point; // .

プログラム全体がメインになります。
さて、メインを書いて、それをエントリのポイントを作ります。
MethodBuilder MTB = TPB.DefineMethod("Main", MethodAttributes.Static, CallingConventions.Any); //static void Main() //Main Procedure
ASM.SetEntryPoint(MTB.GetBaseDefinition());

今、あなたは前に巻かれた変数を初期化する必要があります。 MSDNによると、これは次のように行われます。
ILGenerator MTB_IL=MTB.GetILGenerator();
MTB_IL.Emit(OpCodes.Ldc_I4,30000); // 30000 -
MTB_IL.Emit(OpCodes.Newarr,typeof(byte)); // 30000
MTB_IL.Emit(OpCodes.Stsfld, FDB_1); // Memory


次に、すべてのアクションを説明します:(大量のコードですがコメントアウトされています)
foreach (var t in Src) // , .
{
switch (t)
{
case '>':
{
MTB_IL.Emit(OpCodes.Ldsfld,FDB_2); // POINT
MTB_IL.Emit(OpCodes.Ldc_I4_1); // 1
MTB_IL.Emit(OpCodes.Add); //
MTB_IL.Emit(OpCodes.Stsfld,FDB_2); // Point
break;
}
case '<':
{
MTB_IL.Emit(OpCodes.Ldsfld, FDB_2);// POINT
MTB_IL.Emit(OpCodes.Ldc_I4_1); // 1
MTB_IL.Emit(OpCodes.Sub); //
MTB_IL.Emit(OpCodes.Stsfld, FDB_2); // Point
break;
}
case '+':
{
MTB_IL.Emit(OpCodes.Ldsfld, FDB_1);// MEMORY
MTB_IL.Emit(OpCodes.Ldsfld, FDB_2);// POINT
MTB_IL.Emit(OpCodes.Ldelema, typeof(byte)); // MEMORY[POINT]
MTB_IL.Emit(OpCodes.Dup);
MTB_IL.Emit(OpCodes.Ldobj, typeof(byte));
MTB_IL.Emit(OpCodes.Ldc_I4_1);
MTB_IL.Emit(OpCodes.Add); //
MTB_IL.Emit(OpCodes.Conv_U1);
MTB_IL.Emit(OpCodes.Stobj, typeof(byte));//
break;
}
case '-':
{
MTB_IL.Emit(OpCodes.Ldsfld, FDB_1);// MEMORY
MTB_IL.Emit(OpCodes.Ldsfld, FDB_2);// POINT
MTB_IL.Emit(OpCodes.Ldelema, typeof(byte));// MEMORY[POINT]
MTB_IL.Emit(OpCodes.Dup);
MTB_IL.Emit(OpCodes.Ldobj, typeof(byte));
MTB_IL.Emit(OpCodes.Ldc_I4_1);
MTB_IL.Emit(OpCodes.Sub); //
MTB_IL.Emit(OpCodes.Conv_U1);
MTB_IL.Emit(OpCodes.Stobj, typeof(byte));//
break;
}
case '[':
{
var Lbl = MTB_IL.DefineLabel(); //
MTB_IL.MarkLabel(Lbl); // ,
Scopes.Push(Lbl); // . :)
break;
}
case ']':
{
MTB_IL.Emit(OpCodes.Ldsfld, FDB_1); // 3
MTB_IL.Emit(OpCodes.Ldsfld, FDB_2); //
MTB_IL.Emit(OpCodes.Ldelem_U1); //FDB_1 FDB_2
MTB_IL.Emit(OpCodes.Ldc_I4_0); // 0
MTB_IL.Emit(OpCodes.Ceq); // =0
MTB_IL.Emit(OpCodes.Brtrue,5); //
MTB_IL.Emit(OpCodes.Br,Scopes.Pop()); // . 5 .
break;
}
case '.':
{
MTB_IL.Emit(OpCodes.Ldsfld, FDB_1);// MEMORY
MTB_IL.Emit(OpCodes.Ldsfld, FDB_2);// POINT
MTB_IL.Emit(OpCodes.Ldelem_U1);// MEMORY[POINT]
MTB_IL.EmitCall(OpCodes.Call,typeof(Console).GetMethod("WriteLine",new[] {typeof(char)}),new[] {typeof(char)}); //Console.WriteLine(MEMORY[POINT]);
MTB_IL.Emit(OpCodes.Nop);
break;
}
case ',':
{
MTB_IL.Emit(OpCodes.Ldsfld, FDB_1);// MEMORY
MTB_IL.Emit(OpCodes.Ldsfld, FDB_2);// POINT
MTB_IL.EmitCall(OpCodes.Call, typeof(Console).GetMethod("ReadLine"), new[] { typeof(string) }); //Console.ReadLine();
MTB_IL.Emit(OpCodes.Call,typeof(Convert).GetMethod("ToByte",new[] {typeof(string)})); // .
MTB_IL.Emit(OpCodes.Stelem_I1); //
break;
}
}
}


そしてそれだけです!

MTB_IL.Emit(OpCodes.Ret); //
TPB.CreateType(); //
ASM.Save(Filename); //


ご希望の方は、 VS2010のソースコード+ .slnをご覧ください

UPD:異常なプログラミングに移動

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


All Articles