Megosztás a következőn keresztül:


Továbbfejlesztett #line irányelvek

Jegyzet

Ez a cikk egy funkcióspecifikáció. A specifikáció a funkció tervezési dokumentumaként szolgál. Tartalmazza a specifikáció javasolt módosításait, valamint a funkció tervezése és fejlesztése során szükséges információkat. Ezeket a cikkeket mindaddig közzéteszik, amíg a javasolt specifikációmódosításokat nem véglegesítik, és be nem építik a jelenlegi ECMA-specifikációba.

A szolgáltatás specifikációja és a befejezett implementáció között eltérések lehetnek. Ezeket a különbségeket a vonatkozó nyelvi tervezési értekezlet (LDM) megjegyzései rögzítik.

A funkcióspektusok C# nyelvi szabványba való bevezetésének folyamatáról a specifikációkcímű cikkben olvashat bővebben.

A bajnokkal kapcsolatos kérdés: https://github.com/dotnet/csharplang/issues/4747

Összefoglalás

A fordító az #line irányelvek által meghatározott leképezést alkalmazza a PDB-nek kibocsátott diagnosztikai helyekre és szekvenciapontokra.

Jelenleg csak a sorszámot és a fájl elérési útját lehet leképezni, amíg a kezdő karakter a forráskódból származik. A javaslat lehetővé teszi a teljes span-leképezés megadását.

Motiváció

A C#-forráskódot (például ASP.NET Razort) létrehozó DLL-ek jelenleg nem tudnak pontos forrásleképezést létrehozni #line irányelvek használatával. Ez bizonyos esetekben csökkentett hibakeresési élményt eredményez, mivel a PDB-nek kibocsátott szekvenciapontok nem tudnak az eredeti forráskód pontos helyére leképezést adni.

Például a következő Razor-kód

@page "/"
Time: @DateTime.Now

így generálja a kódot (egyszerűsített):

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

A fenti irányelv a fordító által a _builder.Add(DateTime.Now); utasításhoz kibocsátott szekvenciapontot a 2. sorra képezné le, de az oszlop ki lenne kapcsolva (7 helyett 16).

A Razor-forrásgenerátor valójában helytelenül hozza létre a következő kódot:

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

A szándék az volt, hogy megőrizze a kezdő karaktert, és a diagnosztikai hely leképezéséhez működik. Ez azonban nem működik a szekvenciapontok esetében, mivel #line irányelv csak az azt követő sorrendi pontokra vonatkozik. A _builder.Add(DateTime.Now); utasítás közepén nincs szekvenciapont (a sorrendi pontok csak üres kiértékelési vermet tartalmazó IL-utasításokban bocsáthatók ki). A fenti kód #line 2 irányelve így nincs hatással a létrehozott PDF-fájlra, és a hibakereső nem helyez el töréspontot vagy nem áll le a Razor-oldalon található @DateTime.Now kódrészleten.

A javaslat által kezelt problémák: https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526

Részletes kialakítás

A pp_line irányelvben használt line_indicator szintaxisát a következőképpen módosítjuk:

Aktuális:

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

Javasolt:

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'
    ;

Vagyis: a #line irányelv 5 tizedesjegyet fogadna el (kezdővonal, kezdő karakter, záróvonal, végkaraktér, karaktereltolás), 4 decimális szám (kezdővonal, kezdő karakter, végsor, végkaraktér) vagy egyetlen (sor).

Ha karaktereltolás nincs megadva, az alapértelmezett értéke 0, ellenkező esetben az UTF-16 karakterek számát adja meg. A számnak nem negatívnak kell lennie, és a nem megfeleltetett fájlban lévő #line direktívát követő sor hosszának kisebbnek kell lennie.

(kezdővonal, kezdő karakter)-(záróvonal, záró karakter) a megfeleltetett fájlban megadott időtartamot adja meg. kezdővonal és zárósor pozitív egész számok, amelyek sorszámokat adnak meg. kezdő karakter, záró karakter pozitív egész számok, amelyek UTF-16 karakterszámokat adnak meg. kezdővonal, kezdő karakter, záróvonal, záró karakter 1-alapúak, ami azt jelenti, hogy a fájl első sora és az egyes sorok első UTF-16 karaktere 1-es számhoz van rendelve.

A megvalósítás korlátozná ezeket a számokat, hogy érvényes szekvenciapont-forrástartományt határozzanak meg:

  • kezdővonal – 1 a [0, 0x20000000) tartományon belül van, és nem egyenlő a 0xfeefee.
  • záróvonal – 1 a [0, 0x20000000) tartományon belül van, és nem egyenlő a 0xfeefee.
  • kezdő karakter – Az 1 tartományon belül van [0, 0x10000)
  • végkarakter – 1 a tartományon belül van [0, 0x10000)
  • záróvonal nagyobb vagy egyenlő, mint kezdővonal.
  • kezdővonal egyenlő záróvonallal akkor nagyobb, mint kezdő karakter.

Vegye figyelembe, hogy az irányelv szintaxisában megadott számok 1-alapú számok, de a PDB tényleges spanjai nulla alapúak. Ezért a fenti -1 kiigazítások.

A sorrendpontok leképezett hatóköreit és az irányelv #line alkalmazandó diagnosztikai helyeket az alábbiak szerint számítjuk ki.

Legyen d a #line irányelvet tartalmazó leképezetlen vonal nullaalapú száma. Legyen az L = (kezdet: (kezdővonal - 1, kezdő karakter - 1), vége: (záróvonal - 1, záró karakter - 1)) legyen az irányelv által meghatározott nulla alapú span.

Az M függvény, amely a #line direktívát tartalmazó forrásfájl #line irányelvének hatókörén belüli pozíciót (vonal, karakter) egy leképezett pozícióra (leképezett vonal, leképezett karakter) képezi le, a következőképpen van definiálva:

M(l, c) =

l == d + 1 => (L.start.line + ld – 1, L.start.character + max(ckaraktereltolás, 0))

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

Azokat a szintaxisszerkezeteket, amelyekkel a szekvenciapontok társítva vannak, a fordító implementációja határozza meg, és nem fedi le ezt a specifikációt. A fordító emellett minden egyes szekvenciapont esetében a leképezetlen terjedelmet is meghatározza. Ez az időtartam részben vagy teljesen lefedheti a társított szintaxisszerkezetet.

Miután a fordító meghatározta a le nem képezett terjedelmeket, a fent definiált M függvény lesz alkalmazva azok kezdő és záró pozícióira, kivéve az #line irányelv hatókörébe tartozó összes sorozatpont záró pozícióját, amelynek le nem képezett helye a d + 1 sorban van, és a karakter eltolási értéknél kisebb karakter. Az összes szekvenciapont végpozíciója L.end.

Az [5.i] példa bemutatja, hogy miért szükséges megadni az első szekvenciapont-időtartam végpontpozícióját.

A fenti definíció lehetővé teszi a leképezetlen forráskód generátorának, hogy elkerülje annak intim ismeretét, hogy a C#-nyelv pontos forrásszerkezetei milyen sorrendi pontokat hoznak létre. A #line direktíva hatókörében lévő műveleti pontok térképezett tartományai a megfelelő térképezetlen tartományok relatív helyzetéből származnak az első térképezetlen tartományhoz képest.

A karaktereltolás megadásával a generátor bármilyen egysoros előtagot beszúrhat az első sorba. Ez az előtag egy generált kód, amely nem szerepel a megfeleltetett fájlban. Az ilyen beszúrt előtag hatással van az első leképezetlen szekvenciapont-tartomány értékére. Ezért a következő sorozatpont-szakaszok kezdő karakterét el kell tolni az előtag hosszával (karaktereltolás). Lásd a(z) [2] példát.

kép

Példák

Az egyértelműség kedvéért a példák a spanof('...') és lineof('...') pszeudo-szintaxis használatával fejezik ki a megadott kódrészlet leképezett kezdőpozícióját és sorszámát.

1. Első és későbbi szakaszok

Vegye figyelembe a következő kódot a jobb oldalon felsorolt, leképezetlen nulla alapú sorszámokkal:

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

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

Az irányelv 4 szekvenciapontra vonatkozik a következő leképezetlen és leképezett hatókörökkel: (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. Karaktereltolás

A Razor _builder.Add( 15 hosszúságú előtagot hoz létre (két kezdő szóközt is beleértve).

Borotva:

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

Generált 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) karaktereltolás = 15

Ível:

  • _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: Egyvonalas kiterjedés

Borotva:

@page "/"
Time: @DateTime.Now

Generált C#:

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

4. Razor: Többsoros elrendezés

Borotva:

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

Generált 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: blokkszerkezetek

i. kifejezéseket tartalmazó blokk

Ebben a példában a _builder.Add(Html.Helper(() => utasításhoz kibocsátott IL utasításhoz társított első szekvenciapont leképezett tartományának a létrehozott fájl a.razorHtml.Helper(...) teljes kifejezésére kell kiterjednie. Ez az [1] szabálynak a szekvenciapont végponti pozíciójára történő alkalmazásával érhető el.

@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
)
ii. utasításokat tartalmazó blokk

Meglévő #line line file űrlapot használ, mivel

a) A Razor nem ad hozzá előtagot, b) { nincs jelen a létrehozott fájlban, és nem lehet rajta szekvenciapont, ezért az első le nem képezett szekvenciapont kiterjedése ismeretlen a Razor számára.

A létrehozott fájlban lévő Console kezdő karakterének a Razor-fájlhoz kell igazodnia.

@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
  Console.WriteLine(1);Console.WriteLine(2);
#line hidden
iii. legfelső szintű kódot tartalmazó blokk (@code, @functions)

Meglévő #line line file űrlapot használ, mivel

a) A Razor nem ad hozzá előtagot, b) { nincs jelen a létrehozott fájlban, és nem lehet rajta szekvenciapont, ezért az első le nem képezett szekvenciapont kiterjedése ismeretlen a Razor számára.

A létrehozott fájlban lévő [Parameter] kezdő karakterének a Razor-fájlhoz kell igazodnia.

@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

Meglévő #line line file űrlapot használ, mivel a) a Razor nem ad hozzá előtagot. b) az első le nem képezett szekvenciapont kiterjedése nem ismert a Razor számára (vagy nem kell tudnia).

A generált fájlban lévő kulcsszó kezdő karakterének a Razor-fájlhoz kell igazodnia.

@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