Rozšířené příkazy #line
Poznámka
Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.
Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v poznámkách ke schůzi o návrhu jazyka (LDM) .
Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .
Problém šampiona: https://github.com/dotnet/csharplang/issues/4747
Shrnutí
Kompilátor použije mapování definované direktivou #line
na diagnostická umístění a body sekvence generované do souboru PDB.
V současné době lze mapovat pouze číslo řádku a cestu k souboru, zatímco počáteční znak je odvozen ze zdrojového kódu. Navrhuje se umožnit zadání úplného mapování rozsahu.
Motivace
Seznamy DSLS, které generují zdrojový kód jazyka C# (například ASP.NET Razor), momentálně nemůžou vytvářet přesné mapování zdrojů pomocí direktiv #line
. Výsledkem je zhoršený zážitek z ladění v některých případech, protože sekvenční body generované do PDB nemohou mapovat k přesnému umístění v původním zdrojovém kódu.
Například následující kód Razor
@page "/"
Time: @DateTime.Now
vygeneruje podobný kód (zjednodušený):
#line hidden
void Render()
{
_builder.Add("Time:");
#line 2 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
}
Výše uvedená direktiva by mapovala sekvenční bod vygenerovaný kompilátorem pro příkaz _builder.Add(DateTime.Now);
na řádek 2, ale sloupec by byl nesprávně umístěn (16 místo 7).
Generátor zdroje Razor ve skutečnosti nesprávně vygeneruje následující kód:
#line hidden
void Render()
{
_builder.Add("Time:");
_builder.Add(
#line 2 "page.razor"
DateTime.Now
#line hidden
);
}
Záměrem bylo zachovat počáteční znak a to funguje při mapování umístění diagnostiky. To ale nefunguje u sekvencí bodů, protože #line
direktiva se vztahuje pouze na body sekvence, které ji následují. Uprostřed příkazu _builder.Add(DateTime.Now);
neexistuje bod sekvence (body sekvence lze vygenerovat pouze v pokynech IL s prázdným zásobníkem vyhodnocení). Direktiva #line 2
ve výše uvedeném kódu tedy nemá žádný vliv na vygenerovaný soubor PDB a ladicí program neumisťuje zarážku ani nezastaví fragment kódu @DateTime.Now
na stránce Razor Page.
Problémy vyřešené tímto návrhem: https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526
Podrobný návrh
Syntaxi line_indicator
použité ve směrnici pp_line
měníme takto:
Současný:
line_indicator
: decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
Návrh
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'
;
To znamená, direktiva #line
by přijímala buď 5 desetinných čísel (počáteční řádek, počáteční znak, koncový řádek, koncový znak, znak posunu), 4 desetinná čísla (počáteční řádek, počáteční znak, koncový řádek, koncový znak) nebo jeden znak (řádek).
Pokud není zadán posun znaku jeho výchozí hodnota je 0, jinak určuje počet znaků UTF-16. Číslo musí být nezáporné a méně než délka řádku za direktivou #line v nemapovaném souboru.
(počáteční řádek, počáteční znak)-(koncový řádek, koncový znak) určuje rozsah v mapovaném souboru. počáteční řádek a koncový řádek jsou kladná celá čísla, která určují čísla řádků. počáteční znak, koncový znak jsou kladná celá čísla, která určují čísla znaků UTF-16. počáteční řádek, počáteční znak, koncový řádek, koncový znak jsou 1, což znamená, že první řádek souboru a první znak UTF-16 na každém řádku je přiřazen číslo 1.
Implementace by tato čísla omezila, aby určovala platný zdrojový rozsah sekvenčního bodu .
- Počáteční čára - 1 je v rozsahu [0, 0x20000000) a nerovná se 0xfeefee.
- konec řádku - 1 je v rozsahu [0, 0x20000000) a nerovná se 0xfeefee.
- počáteční znak - 1 je v rozsahu [0, 0x10000)
- koncový znak - 1 je v rozsahu [0, 0x10000)
- koncový řádek je větší nebo roven počátečnímu řádku.
- počáteční řádek je roven koncovému řádku , pak konec znaku je větší než počáteční znak .
Všimněte si, že čísla zadaná v syntaxi direktiv jsou číslována od jedné, ale ve skutečnosti jsou rozpětí v PDB číslována od nuly. Proto úpravy -1 uvedené výše.
Mapované rozsahy sekvenčních bodů a umístění diagnostiky, na které se vztahuje direktiva #line
, se počítají následujícím způsobem.
Nechť d je nulový index nemapované linky obsahující direktivu #line
.
Nechť rozsah L = (začátek: (počáteční řádek - 1, počáteční znak - 1), konec: (koncový řádek - 1, koncový znak - 1)) je nulově založený rozsah určený direktivou.
Funkce M, která mapuje pozici (čáru, znak) v rozsahu direktivy #line
ve zdrojovém souboru obsahující direktivu #line na namapovanou pozici (mapovaný řádek, mapovaný znak), je definován takto:
M(l, c) =
l == d + 1 => (L.start.line + l – d – 1, L.start.character + max(c – posun znaků, 0))
l>d + 1 => (L.start.line + l – d – 1, c)
Syntaxe konstruktorů, ke kterým jsou body sekvence přidruženy, jsou určeny implementací kompilátoru a nejsou pokryty touto specifikací. Kompilátor také rozhoduje o nemapovaném rozsahu každého bodu sekvence. Toto rozpětí může částečně nebo zcela pokrýt příslušnou syntaktickou konstrukci.
Jakmile kompilátor určí nenamapované rozsahy, použije se na jejich počáteční a koncové pozice funkce M definovaná výše, s výjimkou koncové pozice všech bodů sekvencí v rámci direktivy #line, kde nenamapované umístění je na řádku d + 1 a znak je menší než posun znaku. Koncová pozice všech těchto sekvencí bodů je L.end.
Příklad [5.i] ukazuje, proč je nutné poskytnout možnost určit koncovou pozici prvního rozsahu bodu sekvence.
Výše uvedená definice umožňuje generátoru nemapovaného zdrojového kódu vyhnout se podrobnému porozumění tomu, které přesné zdrojové konstrukce jazyka C# generují body sekvence. Mapované rozsahy sekvencí bodů v oboru direktivy
#line
jsou odvozeny z relativní pozice odpovídajících nemapovaných rozsahů do prvního nemapovaného rozsahu.
Zadání posunu znaku umožňuje generátoru vložit libovolnou předponu na první řádek. Tato předpona je vygenerovaný kód, který není v mapovaném souboru. Tato předpona ovlivňuje hodnotu prvního bodu rozsahu nemapované sekvence. Proto musí být počáteční znak následujících rozsahů bodů sekvencí posunut o délku předpony (offset znak). Viz příklad [2].
Příklady
Pro přehlednost příkladů použijte spanof('...')
a lineof('...')
pseudo syntaxi k vyjádření pozice začátku mapovaného rozsahu a čísla řádku zadaného fragmentu kódu.
1. První a následující úseky
Vezměte v úvahu následující kód s nemapovanými čísly řádků založenými na nule uvedených vpravo:
#line (1,10)-(1,15) "a" // 3
A();B( // 4
);C(); // 5
D(); // 6
d = 3 L = (0, 9).. (0, 14)
Existují 4 úseky sekvenčních bodů, na které se vztahuje direktiva, s následujícími nenamapovanými a mapovanými rozpětími: (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. Posun znaku
Razor generuje předponu _builder.Add(
délky 15 (včetně dvou úvodních mezer).
Břitva
@page "/"
@F(() => 1+1,
() => 2+2
)
Vygenerovaný jazyk 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) posun znaku = 15
Rozpětí:
-
_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: Jednořádkové rozpětí
Břitva:
@page "/"
Time: @DateTime.Now
Vygenerovaný jazyk C#:
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
);
}
4. Razor: Víceřádkové rozpětí
Břitva:
@page "/"
@JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}")
Vygenerovaný jazyk 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: blokové konstrukce
i. blok obsahující výrazy
V tomto příkladu musí mapovaný rozsah prvního bodu sekvence, který je přidružený k instrukci IL generované pro příkaz _builder.Add(Html.Helper(() =>
, pokrýt celý výraz Html.Helper(...)
ve vygenerovaném souboru a.razor
. Toho dosáhnete použitím pravidla [1] na koncovou pozici bodu sekvence.
@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. blok obsahující výrazy
Používá existující formulář #line line file
od
a) Razor nepřidá žádnou předponu, b) {
v vygenerovaném souboru neexistuje a na ni nemůže být umístěn bod sekvence, proto rozsah prvního nemapovaného bodu sekvence není pro Razor neznámý.
Počáteční znak Console
vygenerovaného souboru musí být zarovnaný se souborem Razor.
@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
Console.WriteLine(1);Console.WriteLine(2);
#line hidden
iii blok obsahující kód nejvyšší úrovně (@code, @functions)
Používá existující formulář #line line file
od
a) Razor nepřidá žádnou předponu, b) {
v vygenerovaném souboru neexistuje a na ni nemůže být umístěn bod sekvence, proto rozsah prvního nemapovaného bodu sekvence není pro Razor neznámý.
Počáteční znak [Parameter]
vygenerovaného souboru musí být zarovnaný se souborem Razor.
@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
Používá existující formulář #line line file
, protože Razor nepřidává žádnou předponu.
b) rozsah prvního nemapovaného bodu sekvence nemusí být známý Razor (nebo nemusí potřebovat vědět).
Počáteční znak klíčového slova ve vygenerovaném souboru musí být zarovnaný se souborem Razor.
@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
C# feature specifications