Uitgebreide #line-richtlijnen
Notitie
Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.
Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. Deze verschillen worden vastgelegd in de relevante notities van de Language Design Meeting (LDM) .
Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.
Kampioenissue: https://github.com/dotnet/csharplang/issues/4747
Samenvatting
De compiler past de toewijzing toe die is gedefinieerd door #line
instructies voor diagnostische locaties en sequentiepunten die naar de PDB worden verzonden.
Op dit moment kunnen alleen het regelnummer en het bestandspad in kaart worden gebracht, terwijl het beginteken wordt afgeleid van de broncode. Het voorstel is om het specificeren van volledige bereiktoewijzing toe te staan.
Motivatie
DSLs die C#-broncode genereren (zoals ASP.NET Razor) kunnen momenteel geen nauwkeurige brontoewijzing produceren met behulp van #line
instructies. Dit resulteert in gedegradeerde foutopsporingservaring in sommige gevallen omdat de reekspunten die naar de PDB worden verzonden, niet kunnen worden toegewezen aan de exacte locatie in de oorspronkelijke broncode.
Bijvoorbeeld de volgende Razor-code
@page "/"
Time: @DateTime.Now
genereert als volgt code (vereenvoudigd):
#line hidden
void Render()
{
_builder.Add("Time:");
#line 2 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
}
De bovenstaande instructie wijst het sequentiepunt dat door de compiler wordt uitgezonden voor de _builder.Add(DateTime.Now);
-instructie, naar regel 2, maar de kolom zou verkeerd zijn (16 in plaats van 7).
De Razor-brongenerator genereert eigenlijk onjuist de volgende code:
#line hidden
void Render()
{
_builder.Add("Time:");
_builder.Add(
#line 2 "page.razor"
DateTime.Now
#line hidden
);
}
De bedoeling was om het beginteken te behouden en het werkt voor de toewijzing van diagnostische locaties. Dit werkt echter niet voor reekspunten, omdat #line
richtlijn alleen van toepassing is op de volgpunten. Er bevindt zich geen reekspunt in het midden van de _builder.Add(DateTime.Now);
instructie (reekspunten kunnen alleen worden verzonden bij IL-instructies met lege evaluatiestack). De #line 2
instructie in bovenstaande code heeft dus geen invloed op de gegenereerde PDB en het foutopsporingsprogramma plaatst geen onderbrekingspunt of stop op het @DateTime.Now
fragment op de Razor-pagina.
Problemen die in dit voorstel worden behandeld: https://github.com/dotnet/roslyn/issues/43432https://github.com/dotnet/roslyn/issues/46526
Gedetailleerd ontwerp
We passen de syntaxis van line_indicator
, die wordt gebruikt in de pp_line
-richtlijn, als volgt aan:
Actueel:
line_indicator
: decimal_digit+ whitespace file_name
| decimal_digit+
| 'default'
| 'hidden'
;
Voorgestelde:
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'
;
Dat wil zeggen, de #line
-richtlijn accepteert 5 decimale getallen (beginregel, beginteken, eindregel, eindteken, teken offset ), 4 decimale getallen (beginregel, beginteken, eindregel, eindteken), of één (regel).
Als tekenverschil niet is opgegeven, is de standaardwaarde 0, anders wordt het aantal UTF-16 tekens opgegeven. Het getal moet niet-negatief zijn en kleiner dan de lengte van de regel na de #line richtlijn in het niet-toegewezen bestand.
(beginregel, beginteken)-(eindregel, eindteken) geeft een span in het toegewezen bestand op. beginregel en eindregel positieve gehele getallen zijn die regelnummers opgeven. beginteken, eindteken zijn positieve gehele getallen die UTF-16-karakternummers specificeren. beginregel, beginteken, eindregel, eindteken zijn gebaseerd op 1, wat betekent dat de eerste regel van het bestand en het eerste UTF-16-teken op elke regel nummer 1 is toegewezen.
De implementatie beperkt deze getallen zodat ze een geldige reekspuntbronbereik opgeven:
- beginregel - 1 valt binnen het bereik [0, 0x20000000) en is niet gelijk aan 0xfeefee.
- einde regel - 1 valt binnen het bereik [0, 0x20000000) en is niet gelijk aan 0xfeefee.
- beginteken - 1 valt binnen het bereik [0, 0x10000)
- eindkarakter - 1 ligt binnen het bereik van [0, 0x10000)
- eindregel is gelijk aan of groter dan beginregel.
- beginregel gelijk is aan eindregel dan eindteken groter is dan beginteken.
Houd er rekening mee dat de getallen die zijn opgegeven in de syntaxis van de richtlijn 1 zijn, maar dat de werkelijke periodes in de PDB nul zijn. Vandaar de bovenstaande -1 aanpassingen.
De gemapte sequentiepunten en de locaties van diagnosemeldingen waarop de #line
-richtlijn van toepassing is, worden als volgt berekend.
Laat d het nulgebaseerde nummer zijn van de niet-toegewezen regel die de #line
richtlijn bevat.
Laat span L = (begin: (beginregel - 1, beginkarakter - 1), einde: (eindregel - 1, eindkarakter - 1)) nul-gebaseerd zijn zoals aangegeven in de richtlijn.
Functie M waarmee een positie (lijn, teken) binnen het bereik van de #line
-instructie in het bronbestand met de #line-instructie wordt toegewezen aan een toegewezen positie (toegewezen lijn, toegewezen teken) wordt als volgt gedefinieerd:
M(l, c) =
l == d + 1 => (L.start.line + l – d – 1, L.start.character + max(c – tekenverschil, 0))
l>d + 1 => (L.start.line + l – d – 1, c)
De syntaxisconstructies waaraan reekspunten zijn gekoppeld, worden bepaald door de compilerimplementatie en worden niet gedekt door deze specificatie. De compiler bepaalt ook voor elk sequentiepunt de niet-toegewezen reikwijdte. Deze periode kan de bijbehorende syntaxisconstructie gedeeltelijk of volledig bedekken.
Zodra de niet in kaart gebrachte segmenten worden bepaald door de compiler, wordt de hierboven gedefinieerde functie M toegepast op hun begin- en eindposities, met uitzondering van de eindpositie van alle sequentiepunten binnen het bereik van de #line-instructie waarvan de niet in kaart gebrachte locatie zich bevindt op regel d + 1 en een teken kleiner dan de tekenoffset. De eindpositie van al deze reekspunten is L.end.
Voorbeeld [5.i] laat zien waarom het nodig is om de eindpositie van het eerste reekspuntbereik op te geven.
Met de bovenstaande definitie kan de generator van de ongemapte broncode diepgaande kennis vermijden van welke exacte bronconstructies in de C#-taal sequentiepunten produceren. De gemapte sequentiepunten in de scope van de
#line
directive zijn afgeleid van de relatieve positie van de overeenkomstige ongemapte spans tot de eerste ongemapte span.
Door de tekenafstand op te geven kan de generator een enkelregel voorvoegsel op de eerste regel invoegen. Dit voorvoegsel is gegenereerde code die niet aanwezig is in het in kaart gebrachte bestand. Dit ingevoegde voorvoegsel is van invloed op de waarde van het eerste ongemapte sequentiepunt. Daarom moet het beginteken van de daaropvolgende reeks punten worden verschoven met de lengte van het voorvoegsel (tekenverschil). Zie voorbeeld [2].
Voorbeelden
Voor de duidelijkheid gebruiken de voorbeelden spanof('...')
en lineof('...')
pseudosyntaxis om respectievelijk de toegewezen beginpositie en het regelnummer van het opgegeven codefragment uit te drukken.
1. Eerste en volgende segmenten
Houd rekening met de volgende code met ongemapte line-nummers op basis van nul aan de rechterkant:
#line (1,10)-(1,15) "a" // 3
A();B( // 4
);C(); // 5
D(); // 6
d = 3 L = (0, 9).. (0, 14)
Er zijn 4 sequentiepuntenspannen waarop de richtlijn van toepassing is met de volgende niet-toegewezen en toegewezen spannen: (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. Tekenverschuiving
Razor genereert een _builder.Add(
voorvoegsel met een lengte van 15 (inclusief twee voorloopspaties).
Scheermes:
@page "/"
@F(() => 1+1,
() => 2+2
)
Gegenereerde 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) tekenverschil = 15
Spans:
-
_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: Span met één lijn
Scheermes:
@page "/"
Time: @DateTime.Now
Gegenereerde C#:
#line hidden
void Render()
{
_builder.Add("Time:");
#line spanof('DateTime.Now') 15 "page.razor"
_builder.Add(DateTime.Now);
#line hidden
);
}
4. Razor: Multi-line span
Scheermes:
@page "/"
@JsonToHtml(@"
{
""key1"": "value1",
""key2"": "value2"
}")
Gegenereerde 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: blokconstructies
i. blok met expressies
In dit voorbeeld moet het toegewezen gedeelte van het eerste sequentiepunt dat is gekoppeld aan de IL-instructie die wordt uitgevoerd voor de _builder.Add(Html.Helper(() =>
-statement de gehele expressie van Html.Helper(...)
in het gegenereerde bestand a.razor
dekken. Dit wordt bereikt door de toepassing van regel [1] tot de eindpositie van het reekspunt.
@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 met uitspraken
Gebruikt bestaande #line line file
formulier sinds
a) Razor voegt geen voorvoegsel toe, b) {
is niet aanwezig in het gegenereerde bestand en er kan geen sequentiepunt erop geplaatst worden, waardoor het bereik van het eerste niet-toegewezen sequentiepunt onbekend is voor Razor.
Het beginteken van Console
in het gegenereerde bestand moet worden uitgelijnd met het Razor-bestand.
@{Console.WriteLine(1);Console.WriteLine(2);}
#line lineof('@{') "a.razor"
Console.WriteLine(1);Console.WriteLine(2);
#line hidden
iii. blok met code op het hoogste niveau (@code, @functions)
Gebruikt bestaande #line line file
formulier sinds
a) Razor voegt geen voorvoegsel toe, b) {
is niet aanwezig in het gegenereerde bestand en daarom kan er geen volgordepunt op worden geplaatst, waardoor de omvang van het eerste niet-toegewezen volgordepunt onbekend is voor Razor.
Het beginteken van [Parameter]
in het gegenereerde bestand moet worden uitgelijnd met het Razor-bestand.
@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
Gebruikt bestaande #line line file
formulier omdat a) Razor geen voorvoegsel toevoegt.
b) het bereik van het eerste niet-toegewezen reekspunt is mogelijk niet bekend bij Razor (of hoeft het niet te weten).
Het beginteken van het trefwoord in het gegenereerde bestand moet worden uitgelijnd met het Razor-bestand.
@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