Aracılığıyla paylaş


.NET normal ifade kaynak oluşturucuları

Normal ifade veya regex, bir geliştiricinin aranmakta olan bir deseni ifade edebilmesini sağlayan bir dizedir. Bu dize, metni aramanın ve sonuçları arama dizesinden bir alt küme olarak ayıklamanın yaygın bir yoludur. .NET'te System.Text.RegularExpressions ad alanı, Regex örnekleri ve statik yöntemleri tanımlamak ve kullanıcı tanımlı desenlerle eşleşmeler sağlamak için kullanılır. Bu makalede, performansı optimize etmek için Regex örnekleri oluşturmayı sağlamak amacıyla kaynak üretimini kullanmayı öğreneceksiniz.

Not

Mümkün olduğunda, seçeneğini kullanarak RegexOptions.Compiled normal ifadeleri derlemek yerine kaynak tarafından oluşturulan normal ifadeleri kullanın. Kaynak oluşturma, uygulamanızın daha hızlı başlatılmasına, daha hızlı çalışmasına ve daha kırpılabilir hale getirmenize yardımcı olabilir. Kaynak oluşturmanın ne zaman mümkün olduğunu öğrenmek için bkz Ne zaman kullanılır.

Derlenmiş normal ifadeler

yazdığınızda new Regex("somepattern")birkaç şey olur. Belirtilen desen, hem desenin geçerliliğini sağlamak hem de ayrıştırılmış regex'i temsil eden bir iç ağaca dönüştürmek için ayrıştırılır. Daha sonra ağaç çeşitli şekillerde iyileştirildiğinden desen daha verimli bir şekilde yürütülebilecek işlevsel olarak eşdeğer bir varyasyona dönüştürülür. Ağaç, regex yorumlayıcı motoruna, eşleştirme işlemi için nasıl yönergeler sağlanacağını belirten bir dizi opcode ve operant olarak özelleştirilmiş bir forma dönüştürülür. Bir eşleşme gerçekleştirildiğinde, yorumlayıcı bu yönergelerin üzerinde ilerleyerek bunları giriş metnine göre işler. Yeni Regex bir örneğin örneğini oluştururken veya üzerinde Regexstatik yöntemlerden birini çağırırken yorumlayıcı, kullanılan varsayılan altyapıdır.

Siz RegexOptions.Compiled belirttiğinizde, aynı inşaat zamanı çalışmalarının tümü yapılır. Sonuçta elde edilen yönergeler yansıma yayma tabanlı derleyici tarafından birkaç DynamicMethod nesneye yazılan IL yönergelerine dönüştürülür. Bir eşleşme gerçekleştirildiğinde, bu DynamicMethod yöntemler çağrılır. Bu IL temelde tam olarak yorumlayıcının yapacağı şeyi yapar, ancak işlenen tam desen için özelleştirilmiştir. Örneğin, desen içeriyorsa [ac]yorumlayıcı "geçerli konumdaki giriş karakterini bu küme açıklamasında belirtilen kümeyle eşleştir" ifadesini içeren bir işlem kodu görür. Derlenen IL, etkili bir şekilde "geçerli konumdaki giriş karakterini 'a' veya 'c' ile eşleştir" diyen bir kod içerir. Bu özel durum işleme ve desen bilgisini kullanarak optimizasyon yapabilme yeteneği, yorumlayıcıya göre çok daha hızlı eşleşme aktarım hızı sağlayan RegexOptions.Compiled belirtmenin başlıca nedenlerinden bazılarıdır.

'nin çeşitli dezavantajları vardır RegexOptions.Compiled. En etkili olan, oluşturmanın maliyetli olmasıdır. Yorumlayıcı için ödenen maliyetlerin tümüyle aynı olmasının yanı sıra, sonuçta elde edilen RegexNode ağacı ve oluşturulan opcode/işlenenlerin IL'de derlenmesi gerekliliği, önemli ölçüde harcamalar ekler. Oluşturulan IL'nin ilk kullanımda JIT ile derlenmesi gerekir ve bu da başlangıçta daha fazla masrafa neden olur. RegexOptions.Compiled , ilk kullanımdaki ek yüklerle sonraki her kullanımdaki ek yük arasındaki temel dengeyi temsil eder. System.Reflection.Emit kullanımı bununla birlikte belirli ortamlarda RegexOptions.Compiled kullanımını engeller; çeşitli işletim sistemleri dinamik olarak oluşturulan kodların yürütülmesine izin vermez ve bu tür sistemlerde Compiled işlevsiz hale gelir.

Kaynak oluşturma

.NET 7 yeni RegexGenerator bir kaynak oluşturucu tanıttı. Kaynak oluşturucu, derleyiciye takılan ve derleme birimini ek kaynak koduyla genişleten bir bileşendir. .NET SDK'sı, GeneratedRegexAttribute özelliğine sahip ve Regex döndüren kısmi bir yöntemi tanıyan bir kaynak oluşturucu içerir. .NET 9'dan başlayarak, özniteliği kısmi özelliklere de uygulanabilir. Kaynak Üretici, Regex için tüm mantığı içeren yöntemin veya özelliğin bir uygulamasını sağlar. Örneğin, daha önce aşağıdaki gibi bir kod yazmış olabilirsiniz:

private static readonly Regex s_abcOrDefGeneratedRegex =
    new(pattern: "abc|def",
        options: RegexOptions.Compiled | RegexOptions.IgnoreCase);

private static void EvaluateText(string text)
{
    if (s_abcOrDefGeneratedRegex.IsMatch(text))
    {
        // Take action with matching text
    }
}

Kaynak oluşturucuyu kullanmak için önceki kodu aşağıdaki gibi yeniden yazarsınız:

[GeneratedRegex("abc|def", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex AbcOrDefGeneratedRegex();

private static void EvaluateText(string text)
{
    if (AbcOrDefGeneratedRegex().IsMatch(text))
    {
        // Take action with matching text
    }
}

.NET 9'dan başlayarak, GeneratedRegexAttribute öğesini kısmi bir özelliğe, kısmi bir yöntem yerine de uygulayabilirsiniz. Bu, C# 13'ün kısmi özelliklere yönelik desteğiyle etkinleştirilir. Aşağıdaki örnekte özellik eşdeğeri gösterilmektedir:

[GeneratedRegex("abc|def", RegexOptions.IgnoreCase, "en-US")]
private static partial Regex AbcOrDefGeneratedRegexProperty { get; }

private static void EvaluateText(string text)
{
    if (AbcOrDefGeneratedRegexProperty.IsMatch(text))
    {
        // Take action with matching text
    }
}

İpucu

Kaynak oluşturucu RegexOptions.Compiled bayrağını yoksayar, bu yüzden kaynak tarafından üretilen sürümde gerekli değildir.

Oluşturulan uygulama, benzer şekilde bir tek Regex örneğini önbelleğe alır, bu nedenle kodu kullanmak için ek bir önbelleğe alma gerekmez.

Aşağıdaki görüntü, kaynak oluşturucu tarafından yayımlanan Regex alt sınıfına internal oluşturulan önbelleğe alınmış örneğin ekran görüntüsüdür.

Önbelleğe alınmış regex statik alanı

Ama görüldüğü gibi, bu sadece new Regex(...) yapmıyor. Bunun yerine, kaynak oluşturucu C# kodu olarak, IL'de üretilene benzer bir mantıkla, RegexOptions.Compiled'den türetilmiş özel bir uygulamayı oluşturur. RegexOptions.Compiled'nin tüm aktarım hızı performans avantajlarını (hatta daha fazlasını) ve Regex.CompileToAssembly'in başlangıç avantajlarını, ancak CompileToAssembly'nin karmaşıklığı olmadan elde edersiniz. Yayılan kaynak projenizin bir parçasıdır ve bu da kolayca görüntülenebilir ve hata ayıklanabilir olduğu anlamına gelir.

Kaynak tarafından oluşturulan Regex koduyla hata ayıklama

İpucu

Visual Studio'da kısmi yönteminize veya özellik bildiriminize sağ tıklayın ve Tanıma Git'i seçin. Alternatif olarak, Çözüm Gezgini içindeki proje düğümünü seçin ve ardından bu regex oluşturucudan oluşturulan C# kodunu görmek için Dependencies, Analyzers, System.Text.RegularExpressions.Generator, System.Text.RegularExpressions.Generator.RegexGenerator, RegexGenerator.g.cs öğelerini genişletin.

Bunun içinde kesme noktaları ayarlayabilir, adım adım ilerleyebilir ve regex motorunun girişinizle deseninizi tam olarak nasıl işlediğini anlamak için bunu bir öğrenme aracı olarak kullanabilirsiniz. Oluşturucu, ifadeyi bir bakışta anlaşılır hale getirmek ve nerede kullanıldığına yardımcı olmak için üçlü eğik çizgi (XML) açıklamaları bile oluşturur.

Regex'i açıklayan oluşturulan XML açıklamaları

Kaynak tarafından oluşturulan dosyaların içinde

.NET 7 ile hem kaynak oluşturucu hem RegexCompiler de neredeyse tamamen yeniden yazıldı ve temel olarak oluşturulan kodun yapısı değiştirildi. Bu yaklaşım, tek bir uyarı dışında, tüm yapıları işleyecek şekilde genişletilmiştir ve hem RegexCompiler hem de kaynak oluşturucu, yeni yaklaşımı izleyerek çoğunlukla birbirleriyle 1:1 oranında eşleşmektedir. İfadedeki birincil işlevlerden biri için kaynak oluşturucu çıkışını abc|def göz önünde bulundurun:

private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

    // Match with 2 alternative expressions, atomically.
    {
        if (slice.IsEmpty)
        {
            return false; // The input didn't match.
        }

        switch (slice[0])
        {
            case 'A' or 'a':
                if ((uint)slice.Length < 3 ||
                    !slice.Slice(1).StartsWith("bc", StringComparison.OrdinalIgnoreCase)) // Match the string "bc" (ordinal case-insensitive)
                {
                    return false; // The input didn't match.
                }

                pos += 3;
                slice = inputSpan.Slice(pos);
                break;

            case 'D' or 'd':
                if ((uint)slice.Length < 3 ||
                    !slice.Slice(1).StartsWith("ef", StringComparison.OrdinalIgnoreCase)) // Match the string "ef" (ordinal case-insensitive)
                {
                    return false; // The input didn't match.
                }

                pos += 3;
                slice = inputSpan.Slice(pos);
                break;

            default:
                return false; // The input didn't match.
        }
    }

    // The input matched.
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;
}

Kaynak tarafından üretilen kodun amacı, her adımda neler yapıldığını açıklayan açıklamalarla ve genel olarak oluşturucunun bir insan yazmış gibi kod üretmesi gerektiği ilkesine bağlı olarak, takip edilmesi kolay bir yapıya sahip anlaşılabilir kod sunmaktır. Geri izleme söz konusu olsa bile, geri izlemenin yapısı, bir sonraki atlamayı belirtmek için bir yığına güvenmek yerine kodun yapısının bir parçası haline gelir. Örneğin, ifade şu olduğunda oluşturulan eşleşen işlevin kodu aşağıda verilmiştir [ab]*[bc]:

private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    int charloop_starting_pos = 0, charloop_ending_pos = 0;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

    // Match a character in the set [ABab] greedily any number of times.
    //{
        charloop_starting_pos = pos;

        int iteration = slice.IndexOfAnyExcept(Utilities.s_ascii_600000006000000);
        if (iteration < 0)
        {
            iteration = slice.Length;
        }

        slice = slice.Slice(iteration);
        pos += iteration;

        charloop_ending_pos = pos;
        goto CharLoopEnd;

        CharLoopBacktrack:

        if (Utilities.s_hasTimeout)
        {
            base.CheckTimeout();
        }

        if (charloop_starting_pos >= charloop_ending_pos ||
            (charloop_ending_pos = inputSpan.Slice(charloop_starting_pos, charloop_ending_pos - charloop_starting_pos).LastIndexOfAny(Utilities.s_ascii_C0000000C000000)) < 0)
        {
            return false; // The input didn't match.
        }
        charloop_ending_pos += charloop_starting_pos;
        pos = charloop_ending_pos;
        slice = inputSpan.Slice(pos);

        CharLoopEnd:
    //}

    // Advance the next matching position.
    if (base.runtextpos < pos)
    {
        base.runtextpos = pos;
    }

    // Match a character in the set [BCbc].
    if (slice.IsEmpty || ((uint)((slice[0] | 0x20) - 'b') > (uint)('c' - 'b')))
    {
        goto CharLoopBacktrack;
    }

    // The input matched.
    pos++;
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;
}

Kodda, geri izlemenin yapısını, geri dönülecek noktayı gösteren bir CharLoopBacktrack etiketi ve regex'in sonraki bir kısmı başarısız olduğunda o konuma atlamak için kullanılan bir goto etiketi ile görebilirsiniz.

Uygulayan RegexCompiler koda ve kaynak oluşturucuya bakarsanız, bunlar son derece benzer görünür: benzer adlandırılmış yöntemler, benzer çağrı yapısı ve hatta uygulama boyunca benzer açıklamalar. Çoğunlukla, biri IL'de, biri C# dilinde olsa da aynı kodla sonuçlanır. Elbette, C# derleyicisi daha sonra C# öğesini IL'ye çevirmekle sorumludur, bu nedenle her iki durumda da sonuçta elde edilen IL büyük olasılıkla aynı olmayacaktır. Kaynak oluşturucu, C# derleyicisinin çeşitli C# yapılarını daha da iyileştireceği gerçeğinden yararlanarak çeşitli durumlarda buna dayanır. Kaynak oluşturucu RegexCompiler'dan daha optimize edilmiş eşleştirme kodu üretecek birkaç özel durum vardır. Örneğin, önceki örneklerden birinde, kaynak oluşturucunun 'a' için bir dal ve 'b' için başka bir dal içeren bir switch deyimi oluşturduğunu görebilirsiniz. C# derleyicisi, elinde birçok strateji bulunduğundan switch ifadelerini optimize etme konusunda çok iyidir. Bu nedenle, kaynak üreteci, RegexCompiler elementine sahip olmayan özel bir optimizasyona sahiptir. Alternasyonlar için , kaynak oluşturucu tüm dallara bakar ve her dalın farklı bir başlangıç karakteriyle başladığını kanıtlayabilirse, bu ilk karaktere göre bir switch deyimi üretir ve bu alternasyon için herhangi bir geri izleme kodu üretmekten kaçınır.

Bunun biraz daha karmaşık bir örneği aşağıda verilmişti. Değişiklikler, bunları geri izleme altyapıları tarafından daha kolay iyileştirilecek ve daha basit kaynak tarafından oluşturulan koda yol açacak şekilde yeniden düzenlemenin mümkün olup olmadığını belirlemek için daha yoğun bir şekilde analiz edilir. Bu tür bir iyileştirme, dallardan ortak ön eklerin ayıklanmasını destekler ve sıralama önemli olmadığında farklılık atomikse, daha fazla ayıklama yapabilmek için dalların yeniden sıralanması. Bunun, aşağıdaki hafta içi düzeni Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sundayiçin etkisini görebilirsiniz. Bu, şuna benzer bir eşleşen işlev üretir:

private bool TryMatchAtCurrentPosition(ReadOnlySpan<char> inputSpan)
{
    int pos = base.runtextpos;
    int matchStart = pos;
    char ch;
    ReadOnlySpan<char> slice = inputSpan.Slice(pos);

    // Match with 6 alternative expressions, atomically.
    {
        int alternation_starting_pos = pos;

        // Branch 0
        {
            if ((uint)slice.Length < 6 ||
                !slice.StartsWith("monday", StringComparison.OrdinalIgnoreCase)) // Match the string "monday" (ordinal case-insensitive)
            {
                goto AlternationBranch;
            }

            pos += 6;
            slice = inputSpan.Slice(pos);
            goto AlternationMatch;

            AlternationBranch:
            pos = alternation_starting_pos;
            slice = inputSpan.Slice(pos);
        }

        // Branch 1
        {
            if ((uint)slice.Length < 7 ||
                !slice.StartsWith("tuesday", StringComparison.OrdinalIgnoreCase)) // Match the string "tuesday" (ordinal case-insensitive)
            {
                goto AlternationBranch1;
            }

            pos += 7;
            slice = inputSpan.Slice(pos);
            goto AlternationMatch;

            AlternationBranch1:
            pos = alternation_starting_pos;
            slice = inputSpan.Slice(pos);
        }

        // Branch 2
        {
            if ((uint)slice.Length < 9 ||
                !slice.StartsWith("wednesday", StringComparison.OrdinalIgnoreCase)) // Match the string "wednesday" (ordinal case-insensitive)
            {
                goto AlternationBranch2;
            }

            pos += 9;
            slice = inputSpan.Slice(pos);
            goto AlternationMatch;

            AlternationBranch2:
            pos = alternation_starting_pos;
            slice = inputSpan.Slice(pos);
        }

        // Branch 3
        {
            if ((uint)slice.Length < 8 ||
                !slice.StartsWith("thursday", StringComparison.OrdinalIgnoreCase)) // Match the string "thursday" (ordinal case-insensitive)
            {
                goto AlternationBranch3;
            }

            pos += 8;
            slice = inputSpan.Slice(pos);
            goto AlternationMatch;

            AlternationBranch3:
            pos = alternation_starting_pos;
            slice = inputSpan.Slice(pos);
        }

        // Branch 4
        {
            if ((uint)slice.Length < 6 ||
                !slice.StartsWith("fr", StringComparison.OrdinalIgnoreCase) || // Match the string "fr" (ordinal case-insensitive)
                ((((ch = slice[2]) | 0x20) != 'i') & (ch != 'İ')) || // Match a character in the set [Ii\u0130].
                !slice.Slice(3).StartsWith("day", StringComparison.OrdinalIgnoreCase)) // Match the string "day" (ordinal case-insensitive)
            {
                goto AlternationBranch4;
            }

            pos += 6;
            slice = inputSpan.Slice(pos);
            goto AlternationMatch;

            AlternationBranch4:
            pos = alternation_starting_pos;
            slice = inputSpan.Slice(pos);
        }

        // Branch 5
        {
            // Match a character in the set [Ss].
            if (slice.IsEmpty || ((slice[0] | 0x20) != 's'))
            {
                return false; // The input didn't match.
            }

            // Match with 2 alternative expressions, atomically.
            {
                if ((uint)slice.Length < 2)
                {
                    return false; // The input didn't match.
                }

                switch (slice[1])
                {
                    case 'A' or 'a':
                        if ((uint)slice.Length < 8 ||
                            !slice.Slice(2).StartsWith("turday", StringComparison.OrdinalIgnoreCase)) // Match the string "turday" (ordinal case-insensitive)
                        {
                            return false; // The input didn't match.
                        }

                        pos += 8;
                        slice = inputSpan.Slice(pos);
                        break;

                    case 'U' or 'u':
                        if ((uint)slice.Length < 6 ||
                            !slice.Slice(2).StartsWith("nday", StringComparison.OrdinalIgnoreCase)) // Match the string "nday" (ordinal case-insensitive)
                        {
                            return false; // The input didn't match.
                        }

                        pos += 6;
                        slice = inputSpan.Slice(pos);
                        break;

                    default:
                        return false; // The input didn't match.
                }
            }

        }

        AlternationMatch:;
    }

    // The input matched.
    base.runtextpos = pos;
    base.Capture(0, matchStart, pos);
    return true;
}

Kaynak üretici, aynı zamanda IL'ye doğrudan çıkış yaparken mevcut olmayan başka sorunlarla da uğraşmak zorundadır. Birkaç kod örneğine geri bakarsanız, bazı küme ayraçlarının garip bir şekilde açıklama satırı içine alındığını görebilirsiniz. Bu bir hata değil. Kaynak oluşturucu, bu ayraçların açıklama satırı yapılmaması durumunda geri izlemenin yapısının kapsamın dışından bu kapsamın içinde tanımlanan bir etikete atlamak olduğunu fark ediyor; böyle bir etiket için görünür goto olmaz ve kod derlenemez. Bu nedenle, kaynak oluşturucu, bir kapsama engel olmaktan kaçınmalıdır. Bazı durumlarda, burada olduğu gibi kapsamı açıklama satırı yapar. Bunun mümkün olmadığı diğer durumlarda, bazen bunun sorunlu olması durumunda kapsam gerektiren yapılardan (çok deyimli if blok gibi) kaçınabilir.

Kaynak oluşturucu, bir özel durum dışında RegexCompiler'nin işlediği her şeyi işler. İşlemede RegexOptions.IgnoreCaseolduğu gibi, uygulamalar artık yapı zamanında kümeler oluşturmak için bir büyük/küçük harf tablosu kullanır ve geri başvuru eşleştirmesinin bu büyük/küçük harf tablosuna nasıl IgnoreCase başvurması gerekir? Bu tablo, System.Text.RegularExpressions.dll'nin içindedir ve şimdilik, en azından, bu derlemenin dışındaki kodun (kaynak oluşturucu tarafından yayılan kod dahil) buna erişimi yoktur. Bu, kaynak oluşturucuda geri başvuruların işlenmesini zorlaştırır ve bu yüzden desteklenmez. Bu, kaynak oluşturucu tarafından desteklenmeyen ve RegexCompiler tarafından desteklenen bir yapıdır. Bunlardan birine sahip bir desen kullanmaya çalışırsanız (nadirdir), kaynak oluşturucu özel bir uygulama yaymaz ve bunun yerine normal Regex bir örneği önbelleğe almaya geri döner:

Desteklenmeyen düzenli ifade hâlâ önbelleğe alınıyor

Ayrıca, kaynak RegexCompiler oluşturucu da yeni RegexOptions.NonBacktracking'yi desteklemez. Belirttiğiniz takdirde RegexOptions.Compiled | RegexOptions.NonBacktracking, Compiled bayrağı yalnızca yoksayılır ve kaynak oluşturucuya NonBacktracking belirtirseniz, benzer şekilde normal bir Regex örneğini önbelleğe almak için geri döner.

Kullanılması gereken durumlar

Genel olarak, kaynak oluşturucu kullanılabiliyorsa, kullanın. Bugün C# dilinde derleme zamanında bilinen bağımsız değişkenlerle Regex kullanıyorsanız ve özellikle zaten RegexOptions.Compiled kullanıyorsanız (çünkü regex, daha hızlı işleme süresinden yararlanabilecek bir etkin nokta olarak tanımlanmış), kaynak oluşturucuyu kullanmayı tercih etmelisiniz. Kaynak oluşturucu, regex'inize aşağıdaki avantajları sağlar:

  • RegexOptions.Compiled'nin tüm aktarım hızı avantajları.
  • Tüm regex ayrıştırma, çözümleme ve derleme işlemlerini çalışma zamanında yapmak zorunda olmamasının başlangıç avantajları.
  • Regex için oluşturulan kodla önceden derleme seçeneği.
  • Daha iyi hata ayıklama ve regex'i anlama.
  • Uygulamanızın kırpılmış boyutunu azaltma olanağı, RegexCompiler ile ilişkili büyük kod bölümlerini kırparak (ve hatta yansıma yayını bile kırparak).

Uygulayıcının özel bir uygulama oluşturamadığı RegexOptions.NonBacktracking gibi bir seçenekle kullanıldığında, yine de uygulamayı tanımlayan önbelleğe alma ve XML açıklamaları yayar, bu da onun değerli olmasını sağlar. Kaynak oluşturucunun temel dezavantajı, derlemenize ek kod yaymasıdır, bu nedenle boyutun artması olasılığı vardır. Uygulamanızda ne kadar çok regex veya düzenli ifade varsa ve bunlar ne kadar büyükse, bunlar için o kadar çok kod yayılır. Bazı durumlarda, RegexOptions.Compiled gereksiz olabileceği gibi, kaynak oluşturucu da gereksiz olabilir. Örneğin, yalnızca nadiren gereken ve aktarım hızının önemli olmadığı bir regex'iniz varsa, yalnızca bu düzensiz kullanım için yorumlayıcıya güvenmek daha yararlı olabilir.

Önemli

.NET 7, kaynak oluşturucuya dönüştürülebilecek Regex tespit eden bir çözümleyici ve dönüştürme işlemini sizin için gerçekleştiren bir düzeltici içerir.

RegexGenerator çözümleyicisi ve düzelticisi

Ayrıca bkz.