Aracılığıyla paylaş


Gelişmiş #line yönergeleri

Not

Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.

Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar, ilgilidil tasarım toplantısı (LDM) notlarında yakalanır.

Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek için belirtimleri makalesinde bulabilirsiniz.

Şampiyonluk sorunu: https://github.com/dotnet/csharplang/issues/4747

Özet

Derleyici, #line yönergeleri tarafından tanımlanan eşlemeyi PDB'ye yayılan tanılama konumlarına ve sıra noktalarına uygular.

Şu anda kaynak koddan başlangıç karakteri çıkarılırken yalnızca satır numarası ve dosya yolu eşlenebilir. Öneri, tam kapsam eşlemesinin belirlenmesine izin verilmesidir.

Motivasyon

C# kaynak kodu (ASP.NET Razor gibi) oluşturan DSL'ler şu anda #line yönergelerini kullanarak hassas kaynak eşlemesi oluşturamaz. Bazı durumlarda PDB'ye yayılan sıra noktaları özgün kaynak kodundaki kesin konuma eşlenemediklerinde bu hata ayıklama deneyiminin düşmesine neden olur.

Örneğin, aşağıdaki Razor kodu

@page "/"
Time: @DateTime.Now

gibi kod oluşturur (basitleştirilmiş):

#line hidden
void Render()
{
   _builder.Add("Time:");
#line 2 "page.razor"
   _builder.Add(DateTime.Now);
#line hidden
}

Yukarıdaki yönerge, _builder.Add(DateTime.Now); deyimi için derleyici tarafından yayılan sıra noktasını 2. satırla eşler, ancak sütun kapalı olur (7 yerine 16).

Razor kaynak oluşturucu aslında yanlış aşağıdaki kodu oluşturur:

#line hidden
void Render()
{
   _builder.Add("Time:");
   _builder.Add(
#line 2 "page.razor"
      DateTime.Now
#line hidden
);
}

Amacı, başlangıç karakterini korumaktı ve bu, tanı konumu eşleştirmesi için işe yarar. Ancak, #line yönergesi yalnızca onu izleyen dizi noktaları için geçerli olduğundan bu işlem dizi noktaları için çalışmaz. _builder.Add(DateTime.Now); deyiminin ortasında bir dizi noktası yoktur (sıra noktaları yalnızca boş değerlendirme yığınına sahip IL yönergelerinde yayılabilir). Oluşturulan PDB dosyası üzerinde yukarıdaki koddaki #line 2 yönergesinin hiçbir etkisi yoktur ve hata ayıklayıcı, Razor sayfasındaki @DateTime.Now kod parçacığı için bir kesme noktası koymaz veya durmaz.

Bu teklif tarafından giderilen sorunlar: https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526

Ayrıntılı tasarım

pp_line yönergesinde kullanılan line_indicator söz dizimini şu şekilde düzeltiyoruz:

Geçerli:

line_indicator
    : decimal_digit+ whitespace file_name
    | decimal_digit+
    | 'default'
    | 'hidden'
    ;

Önerilen:

line_indicator
    : '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' whitespace decimal_digit+ whitespace file_name
    | '(' decimal_digit+ ',' decimal_digit+ ')' '-' '(' decimal_digit+ ',' decimal_digit+ ')' whitespace file_name
    | decimal_digit+ whitespace file_name
    | decimal_digit+
    | 'default'
    | 'hidden'
    ;

Yani, #line yönergesi 5 ondalık sayı (başlangıç satırı, başlangıç karakteri, bitiş çizgisi, bitiş karakteri, karakter uzaklığıkabul eder ) ), 4 ondalık sayı (başlangıç satırı, başlangıç karakteri, bitiş çizgisi, bitiş karakteri) veya tek bir sayı (satır).

karakter uzaklığı belirtilmezse varsayılan değeri 0'dır, aksi takdirde UTF-16 karakter sayısını belirtir. Sayı negatif olmamalıdır ve eşlenmemiş dosyadaki #line yönergesini izleyen satırın uzunluğu daha az olmalıdır.

(başlangıç satırı, başlangıç karakteri)-(bitiş satırı, bitiş karakteri) eşlenen dosyada bir yayılma alanı belirtir. başlangıç satırı ve bitiş çizgisi, satır numaralarını belirten pozitif tamsayılardır. başlangıç karakteribitiş karakteri UTF-16 karakter numaralarını belirten pozitif tamsayılardır. başlangıç satırı, başlangıç karakteri, bitiş satırı, bitiş karakteri 1 tabanlıdır, yani dosyanın ilk satırına ve her satırdaki ilk UTF-16 karakterine 1 numara atanır.

Uygulama, geçerli bir sıra noktası kaynak aralığı belirtecek şekilde bu sayıları kısıtlayacaktır:

  • başlangıç satırı - 1 [0, 0x20000000) aralığındadır ve 0xfeefee eşit değildir.
  • bitiş çizgisi - 1 [0, 0x20000000) aralığındadır ve 0xfeefee eşit değildir.
  • başlangıç karakteri - 1 aralığı içinde [0, 0x10000)
  • bitiş karakteri - 1 [0, 0x10000) aralığındadır
  • bitiş çizgisibaşlangıç satırına eşit veya büyüktür.
  • başlangıç satırıbitiş çizgisine eşittir sonra bitiş karakteribaşlangıç karakterinden büyüktür.

Yönerge söz diziminde belirtilen sayıların 1 tabanlı sayılar olduğunu, ancak PDB'deki gerçek aralıkların sıfır tabanlı olduğunu unutmayın. Bu nedenle yukarıdaki -1 ayarlamaları.

Dizi noktalarının eşlenen aralıkları ve #line yönergesinin geçerli olduğu tanılama konumları aşağıdaki gibi hesaplanır.

d#line yönergesini içeren eşlenmemiş satırın sıfır tabanlı sayısı olmasına izin verin. Let span L = (başlangıç: (başlangıç satırı - 1, başlangıç karakteri - 1), end: (bitiş satırı - 1, bitiş karakteri - 1)) yönergesi tarafından belirtilen sıfır tabanlı yayılma alanıdır.

#line yönergesini içeren kaynak dosyadaki #line yönergesi kapsamındaki bir konumu (satır, karakter) eşlenen konuma (eşlenmiş çizgi, eşlenmiş karakter) eşleyen M işlevi aşağıdaki gibi tanımlanır:

M(l, c) =

l == d + 1 => (L.start.line + ld – 1, L.start.character + max(ckarakter uzaklığı, 0))

l>d + 1 => (L.start.line + ld – 1, c)

Dizi noktalarının ilişkilendirdiği söz dizimi yapıları, derleyici uygulaması tarafından belirlenir ve bu belirtim kapsamında değildir. Derleyici ayrıca her bir sıra noktası için eşlenmemiş aralığa da karar verir. Bu aralık, ilişkili söz dizimi yapısını kısmen veya tamamen kapsayabilir.

Eşlenmemiş span'lar derleyici tarafından belirlendikten sonra, yukarıda tanımlanan M işlevi, başlangıç ve bitiş konumlarına uygulanır; ancak bu uygulama, eşlenmemiş konumu satır d + 1'de ve karakter ofsetinden daha az olan karakterle sıralanan, #line yönergesi kapsamındaki tüm sıra noktalarının bitiş konumu için geçerli değildir. Tüm bu sıra noktalarının bitiş konumu L.end .

[5.i] örneği, ilk sıra noktası aralığının bitiş konumunu belirtme özelliğinin neden gerekli olduğunu gösterir.

Yukarıdaki tanım eşlenmemiş kaynak kodunun oluşturucusunun C# dilinin hangi kaynak yapılarının dizi noktaları ürettiği hakkında ayrıntılı bilgi sahibi olmamasını sağlar. #line yönergesi kapsamındaki dizi noktalarının eşlenmiş yayılmaları, karşılık gelen eşlenmemiş aralıkların ilk eşlenmemiş aralığa göreli konumlarından türetilir.

karakter uzaklığı belirtilmesi, oluşturucunun ilk satıra herhangi bir tek satırlık ön ek eklemesine olanak tanır. Eşlenen dosyada mevcut olmayan bu ön ek, oluşturulmuş bir koddur. Bu tür eklenen ön ek, ilk eşlenmemiş dizi noktası aralığının değerini etkiler. Bu nedenle sonraki sıra noktası aralıklarının başlangıç karakteri, ön ekin uzunluğuna göre kaydırılmalı (karakter kaydırma). Bkz. örnek [2].

resim

Örnekler

Açıklık sağlamak için örnekler, belirtilen kod parçacığının eşlenmiş aralık başlangıç konumunu ve satır numarasını ifade etmek için spanof('...') ve lineof('...') sözde söz dizimini kullanır.

1. İlk ve sonraki aralıklar

Sağ tarafta eşlenmemiş sıfır tabanlı satır numaralarının listelendiği aşağıdaki kodu göz önünde bulundurun:

#line (1,10)-(1,15) "a" // 3
  A();B(                // 4
);C();                  // 5
    D();                // 6

d = 3 L = (0, 9).. (0, 14)

Yönergenin şu eşlenmemiş ve eşlenmiş aralıklarla uygulandığı 4 sıra noktası aralığı vardır: (4, 2).. (4, 5) => (0, 9).. (0, 14) (4, 6).. (5, 1) => (0, 15).. (1, 1) (5, 2).. (5, 5) => (1, 2).. (1, 5) (6, 4).. (6, 7) => (2, 4).. (2, 7)

2. Karakter uzaklığı

Razor, 15 karakter uzunluğunda _builder.Add( ön eki oluşturur (baştaki iki boşluk dahil).

Jilet:

@page "/"                                  
@F(() => 1+1, 
   () => 2+2
) 

Oluşturulan C#:

#line hidden
void Render()            
{ 
#line spanof('F(...)') 15 "page.razor"  // 4
  _builder.Add(F(() => 1+1,            // 5
  () => 2+2                            // 6
));                                    // 7
#line hidden
}
);
}

d = 4 L = (1, 1).. (3,0) karakter uzaklığı = 15

Aralıklar

  • _builder.Add(F(…)); =>F(…): (5, 2).. (7, 2) => (1, 1).. (3, 0)
  • 1+1 =>1+1: (5, 23).. (5, 25) => (1, 9).. (1, 11)
  • 2+2 =>2+2: (6, 7).. (6, 9) => (2, 7).. (2, 9)

3. Razor: Tek satırlı bölüm

Jilet:

@page "/"
Time: @DateTime.Now

Oluşturulan C#:

#line hidden
void Render()
{
  _builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
  _builder.Add(DateTime.Now);
#line hidden
);
}

4. Razor: Çok hatlı span

Jilet:

@page "/"                                  
@JsonToHtml(@"
{
  ""key1"": "value1",
  ""key2"": "value2"
}") 

Oluşturulan C#:

#line hidden
void Render()
{
  _builder.Add("Time:");
#line spanof('JsonToHtml(@"...")') 15 "page.razor"
  _builder.Add(JsonToHtml(@"
{
  ""key1"": "value1",
  ""key2"": "value2"
}"));
#line hidden
}
);
}

5. Razor: blok yapıları

i. ifadeleri içeren blok

Bu örnekte, _builder.Add(Html.Helper(() => deyimi için üretilen IL yönergesiyle ilişkili ilk sıra noktasının eşlenmiş aralığının, oluşturulan dosya a.razor'deki Html.Helper(...) ifadesinin tamamını kapsaması gerekir. Bu, [1] kuralının sıra noktasının bitiş konumuna uygulanmasıyla elde edilir.

@Html.Helper(() => 
{
    <p>Hello World</p>
    @DateTime.Now
})
#line spanof('Html.Helper(() => { ... })') 13 "a.razor"
_builder.Add(Html.Helper(() => 
#line lineof('{') "a.razor"
{
#line spanof('DateTime.Now') 13 "a.razor"
_builder.Add(DateTime.Now);
#line lineof('}') "a.razor"
}
#line hidden
)
ıı. deyimleri içeren blok

Şu tarihten itibaren mevcut #line line file formunu kullanır:

a) Razor herhangi bir ön ek eklemez, b) oluşturulan dosyada { yoktur ve üzerine yerleştirilmiş bir dizi noktası olamaz, bu nedenle ilk eşlenmemiş sıra noktasının aralığı Razor tarafından bilinmez.

Oluşturulan dosyadaki Console başlangıç karakteri Razor dosyasıyla hizalanmalıdır.

@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
  Console.WriteLine(1);Console.WriteLine(2);
#line hidden
iii. üst düzey kod içeren blok (@code, @functions)

Şu tarihten itibaren mevcut #line line file formunu kullanır:

a) Razor herhangi bir ön ek eklemez, b) oluşturulan dosyada { yoktur ve üzerine yerleştirilmiş bir dizi noktası olamaz, bu nedenle ilk eşlenmemiş sıra noktasının aralığı Razor tarafından bilinmez.

Oluşturulan dosyadaki [Parameter] başlangıç karakteri Razor dosyasıyla hizalanmalıdır.

@code {
    [Parameter]
    public int IncrementAmount { get; set; }
}
#line lineof('[') "a.razor"
    [Parameter]
    public int IncrementAmount { get; set; }
#line hidden

6. Razor: @for, @foreach, @while, @do, @if, @switch, @using, @try, @lock

Var olan #line line file formunu kullanır çünkü a) Razor herhangi bir ön ek eklemez. b) ilk eşlenmemiş sıra noktasının aralığı Razor tarafından bilinmiyor olabilir (veya bilmesi gerekmez).

Oluşturulan dosyadaki anahtar sözcüğün başlangıç karakteri Razor dosyasıyla hizalanmalıdır.

@for (var i = 0; i < 10; i++)
{
}
@if (condition)
{
}
else
{
}
#line lineof('for') "a.razor"
 for (var i = 0; i < 10; i++)
{
}
#line lineof('if') "a.razor"
 if (condition)
{
}
else
{
}
#line hidden