зеркало из https://github.com/dotnet/razor.git
Use PooledArrayBuilder<SyntaxToken> throughout parsers and tokenizer (#10095)
@ToddGrun shared a couple PerfView stacks with me where the Razor compiler was allocating `List<SyntaxToken>` instances in the tokenizer. To remove those allocation, this change replaces `List<SyntaxToken>s` throughout the parsers and tokenizer, and uses `PooledArrayBuilder<SyntaxToken>` instead. In addition, I removed a couple of `Concat` calls and replaced them with pooled `StringBuilders`.
This commit is contained in:
Коммит
d4a6aaafa0
|
@ -151,7 +151,7 @@
|
|||
<NuGetSolutionRestoreManagerInteropVersion>4.8.0</NuGetSolutionRestoreManagerInteropVersion>
|
||||
<StreamJsonRpcPackageVersion>2.17.11</StreamJsonRpcPackageVersion>
|
||||
<SystemRuntimeInteropServicesRuntimePackageVersion>4.3.0</SystemRuntimeInteropServicesRuntimePackageVersion>
|
||||
<Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion>3.11.0-beta1.24128.1</Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion>
|
||||
<Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion>3.11.0-beta1.24170.2</Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion>
|
||||
<Tooling_MicrosoftCodeAnalysisBannedApiAnalyzersPackageVersion>$(Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion)</Tooling_MicrosoftCodeAnalysisBannedApiAnalyzersPackageVersion>
|
||||
<Tooling_RoslynDiagnosticsAnalyzersPackageVersion>$(Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion)</Tooling_RoslynDiagnosticsAnalyzersPackageVersion>
|
||||
<Tooling_MicrosoftVisualStudioLanguageServicesPackageVersion>$(MicrosoftVisualStudioLanguageServicesPackageVersion)</Tooling_MicrosoftVisualStudioLanguageServicesPackageVersion>
|
||||
|
|
|
@ -67,15 +67,15 @@ public class TokenizerLookaheadTest : HtmlTokenizerTestBase
|
|||
|
||||
// Act
|
||||
var i = 3;
|
||||
IEnumerable<SyntaxToken> previousTokens = null;
|
||||
SyntaxToken[] previousTokens = null;
|
||||
var tokenFound = tokenizer.LookaheadUntil((s, p) =>
|
||||
{
|
||||
previousTokens = p;
|
||||
previousTokens = p.ToArray();
|
||||
return --i == 0;
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(4, previousTokens.Count());
|
||||
Assert.Equal(4, previousTokens.Length);
|
||||
|
||||
// For the very first element, there will be no previous items, so null is expected
|
||||
var orderIndex = 0;
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Collections.Immutable;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
||||
|
@ -228,7 +229,8 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
{
|
||||
NextToken();
|
||||
|
||||
var precedingWhitespace = ReadWhile(IsSpacingTokenIncludingNewLinesAndComments);
|
||||
using var precedingWhitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(IsSpacingTokenIncludingNewLinesAndComments, ref precedingWhitespace.AsRef());
|
||||
|
||||
// We are usually called when the other parser sees a transition '@'. Look for it.
|
||||
SyntaxToken? transitionToken = null;
|
||||
|
@ -260,7 +262,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
if (At(SyntaxKind.LeftBrace))
|
||||
{
|
||||
// This is a statement. We want to preserve preceding whitespace in the output.
|
||||
Accept(precedingWhitespace);
|
||||
Accept(in precedingWhitespace);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
|
||||
var statementBody = ParseStatementBody();
|
||||
|
@ -270,7 +272,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
else if (At(SyntaxKind.LeftParenthesis))
|
||||
{
|
||||
// This is an explicit expression. We want to preserve preceding whitespace in the output.
|
||||
Accept(precedingWhitespace);
|
||||
Accept(in precedingWhitespace);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
|
||||
var expressionBody = ParseExplicitExpressionBody();
|
||||
|
@ -279,11 +281,11 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
else if (At(SyntaxKind.Identifier))
|
||||
{
|
||||
if (!TryParseDirective(builder, precedingWhitespace, transition, CurrentToken.Content))
|
||||
if (!TryParseDirective(builder, in precedingWhitespace, transition, CurrentToken.Content))
|
||||
{
|
||||
// Not a directive.
|
||||
// This is an implicit expression. We want to preserve preceding whitespace in the output.
|
||||
Accept(precedingWhitespace);
|
||||
Accept(in precedingWhitespace);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
|
||||
if (string.Equals(
|
||||
|
@ -304,12 +306,12 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
else if (At(SyntaxKind.Keyword))
|
||||
{
|
||||
if (!TryParseDirective(builder, precedingWhitespace, transition, CurrentToken.Content) &&
|
||||
!TryParseKeyword(builder, precedingWhitespace, transition))
|
||||
if (!TryParseDirective(builder, in precedingWhitespace, transition, CurrentToken.Content) &&
|
||||
!TryParseKeyword(builder, in precedingWhitespace, transition))
|
||||
{
|
||||
// Not a directive or keyword.
|
||||
// This is an implicit expression. We want to preserve preceding whitespace in the output.
|
||||
Accept(precedingWhitespace);
|
||||
Accept(in precedingWhitespace);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
|
||||
// Not a directive or a special keyword. Just parse as an implicit expression.
|
||||
|
@ -324,7 +326,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
{
|
||||
// Invalid character after transition.
|
||||
// Preserve the preceding whitespace in the output
|
||||
Accept(precedingWhitespace);
|
||||
Accept(in precedingWhitespace);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
|
||||
chunkGenerator = new ExpressionChunkGenerator();
|
||||
|
@ -820,7 +822,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
break;
|
||||
case SyntaxKind.Keyword:
|
||||
if (!TryParseKeyword(builder, whitespace: null, transition: null))
|
||||
if (!TryParseKeyword(builder))
|
||||
{
|
||||
ParseStandardStatement(builder);
|
||||
}
|
||||
|
@ -911,20 +913,23 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
while (!EndOfFile)
|
||||
{
|
||||
var bookmark = CurrentStart.AbsoluteIndex;
|
||||
var read = ReadWhile(static token =>
|
||||
token.Kind != SyntaxKind.Semicolon &&
|
||||
token.Kind != SyntaxKind.RazorCommentTransition &&
|
||||
token.Kind != SyntaxKind.Transition &&
|
||||
token.Kind != SyntaxKind.LeftBrace &&
|
||||
token.Kind != SyntaxKind.LeftParenthesis &&
|
||||
token.Kind != SyntaxKind.LeftBracket &&
|
||||
token.Kind != SyntaxKind.RightBrace);
|
||||
using var read = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(
|
||||
static token =>
|
||||
token.Kind != SyntaxKind.Semicolon &&
|
||||
token.Kind != SyntaxKind.RazorCommentTransition &&
|
||||
token.Kind != SyntaxKind.Transition &&
|
||||
token.Kind != SyntaxKind.LeftBrace &&
|
||||
token.Kind != SyntaxKind.LeftParenthesis &&
|
||||
token.Kind != SyntaxKind.LeftBracket &&
|
||||
token.Kind != SyntaxKind.RightBrace,
|
||||
ref read.AsRef());
|
||||
|
||||
if ((!Context.FeatureFlags.AllowRazorInAllCodeBlocks && At(SyntaxKind.LeftBrace)) ||
|
||||
At(SyntaxKind.LeftParenthesis) ||
|
||||
At(SyntaxKind.LeftBracket))
|
||||
{
|
||||
Accept(read);
|
||||
Accept(in read);
|
||||
if (Balance(builder, BalancingModes.AllowCommentsAndTemplates | BalancingModes.BacktrackOnFailure))
|
||||
{
|
||||
TryAccept(SyntaxKind.RightBrace);
|
||||
|
@ -938,31 +943,31 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
else if (Context.FeatureFlags.AllowRazorInAllCodeBlocks && At(SyntaxKind.LeftBrace))
|
||||
{
|
||||
Accept(read);
|
||||
Accept(in read);
|
||||
return;
|
||||
}
|
||||
else if (At(SyntaxKind.Transition) && (NextIs(SyntaxKind.LessThan, SyntaxKind.Colon)))
|
||||
{
|
||||
Accept(read);
|
||||
Accept(in read);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
ParseTemplate(builder);
|
||||
}
|
||||
else if (At(SyntaxKind.RazorCommentTransition))
|
||||
{
|
||||
Accept(read);
|
||||
Accept(in read);
|
||||
AcceptMarkerTokenIfNecessary();
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
builder.Add(ParseRazorComment());
|
||||
}
|
||||
else if (At(SyntaxKind.Semicolon))
|
||||
{
|
||||
Accept(read);
|
||||
Accept(in read);
|
||||
AcceptAndMoveNext();
|
||||
return;
|
||||
}
|
||||
else if (At(SyntaxKind.RightBrace))
|
||||
{
|
||||
Accept(read);
|
||||
Accept(in read);
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
@ -1006,12 +1011,16 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
}
|
||||
|
||||
protected bool TryParseDirective(in SyntaxListBuilder<RazorSyntaxNode> builder, IReadOnlyList<SyntaxToken> whitespace, CSharpTransitionSyntax transition, string directive)
|
||||
private bool TryParseDirective(
|
||||
in SyntaxListBuilder<RazorSyntaxNode> builder,
|
||||
ref readonly PooledArrayBuilder<SyntaxToken> whitespace,
|
||||
CSharpTransitionSyntax transition,
|
||||
string directive)
|
||||
{
|
||||
if (_directiveParserMap.TryGetValue(directive, out var handler))
|
||||
{
|
||||
// This is a directive. We don't want to generate the preceding whitespace in the output.
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
builder.Add(OutputTokensAsEphemeralLiteral());
|
||||
|
||||
chunkGenerator = SpanChunkGenerator.Null;
|
||||
|
@ -1785,27 +1794,31 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
// qualified-identifier . identifier
|
||||
protected bool TryParseQualifiedIdentifier(out int identifierLength)
|
||||
{
|
||||
using var tokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
|
||||
var currentIdentifierLength = 0;
|
||||
var expectingDot = false;
|
||||
var tokens = ReadWhile(token =>
|
||||
{
|
||||
var type = token.Kind;
|
||||
if ((expectingDot && type == SyntaxKind.Dot) ||
|
||||
(!expectingDot && type == SyntaxKind.Identifier))
|
||||
ReadWhile(
|
||||
token =>
|
||||
{
|
||||
expectingDot = !expectingDot;
|
||||
return true;
|
||||
}
|
||||
var type = token.Kind;
|
||||
if ((expectingDot && type == SyntaxKind.Dot) ||
|
||||
(!expectingDot && type == SyntaxKind.Identifier))
|
||||
{
|
||||
expectingDot = !expectingDot;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type != SyntaxKind.Whitespace &&
|
||||
type != SyntaxKind.NewLine)
|
||||
{
|
||||
expectingDot = false;
|
||||
currentIdentifierLength += token.Content.Length;
|
||||
}
|
||||
if (type != SyntaxKind.Whitespace &&
|
||||
type != SyntaxKind.NewLine)
|
||||
{
|
||||
expectingDot = false;
|
||||
currentIdentifierLength += token.Content.Length;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
},
|
||||
ref tokens.AsRef());
|
||||
|
||||
identifierLength = currentIdentifierLength;
|
||||
var validQualifiedIdentifier = expectingDot;
|
||||
|
@ -1896,18 +1909,18 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
}
|
||||
|
||||
private bool TryParseKeyword(in SyntaxListBuilder<RazorSyntaxNode> builder, IReadOnlyList<SyntaxToken>? whitespace, CSharpTransitionSyntax? transition)
|
||||
private bool TryParseKeyword(
|
||||
in SyntaxListBuilder<RazorSyntaxNode> builder,
|
||||
ref readonly PooledArrayBuilder<SyntaxToken> whitespace,
|
||||
CSharpTransitionSyntax? transition)
|
||||
{
|
||||
var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
|
||||
Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword && result.HasValue);
|
||||
if (_keywordParserMap.TryGetValue(result!.Value, out var handler))
|
||||
{
|
||||
if (whitespace != null)
|
||||
{
|
||||
// This is a keyword. We want to preserve preceding whitespace in the output.
|
||||
Accept(whitespace);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
}
|
||||
// This is a keyword. We want to preserve preceding whitespace in the output.
|
||||
Accept(in whitespace);
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
|
||||
handler(builder, transition);
|
||||
return true;
|
||||
|
@ -1916,6 +1929,19 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
return false;
|
||||
}
|
||||
|
||||
private bool TryParseKeyword(in SyntaxListBuilder<RazorSyntaxNode> builder)
|
||||
{
|
||||
var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
|
||||
Debug.Assert(CurrentToken.Kind == SyntaxKind.Keyword && result.HasValue);
|
||||
if (_keywordParserMap.TryGetValue(result!.Value, out var handler))
|
||||
{
|
||||
handler(builder, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AtBooleanLiteral()
|
||||
{
|
||||
var result = CSharpTokenizer.GetTokenKeyword(CurrentToken);
|
||||
|
@ -2103,12 +2129,13 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
private void ParseAfterIfClause(SyntaxListBuilder<RazorSyntaxNode> builder)
|
||||
{
|
||||
// Grab whitespace and razor comments
|
||||
var whitespace = SkipToNextImportantToken(builder);
|
||||
using var whitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
SkipToNextImportantToken(builder, ref whitespace.AsRef());
|
||||
|
||||
// Check for an else part
|
||||
if (At(CSharpKeyword.Else))
|
||||
{
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
Assert(CSharpKeyword.Else);
|
||||
ParseElseClause(builder);
|
||||
}
|
||||
|
@ -2116,7 +2143,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
{
|
||||
// No else, return whitespace
|
||||
PutCurrentBack();
|
||||
PutBack(whitespace);
|
||||
PutBack(in whitespace);
|
||||
SetAcceptedCharacters(AcceptedCharactersInternal.Any);
|
||||
}
|
||||
}
|
||||
|
@ -2165,19 +2192,20 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
private void ParseAfterTryClause(in SyntaxListBuilder<RazorSyntaxNode> builder)
|
||||
{
|
||||
// Grab whitespace
|
||||
var whitespace = SkipToNextImportantToken(builder);
|
||||
using var whitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
SkipToNextImportantToken(builder, ref whitespace.AsRef());
|
||||
|
||||
// Check for a catch or finally part
|
||||
if (At(CSharpKeyword.Catch))
|
||||
{
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
Assert(CSharpKeyword.Catch);
|
||||
ParseFilterableCatchBlock(builder);
|
||||
ParseAfterTryClause(builder);
|
||||
}
|
||||
else if (At(CSharpKeyword.Finally))
|
||||
{
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
Assert(CSharpKeyword.Finally);
|
||||
ParseUnconditionalBlock(builder);
|
||||
}
|
||||
|
@ -2185,7 +2213,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
{
|
||||
// Return whitespace and end the block
|
||||
PutCurrentBack();
|
||||
PutBack(whitespace);
|
||||
PutBack(in whitespace);
|
||||
SetAcceptedCharacters(AcceptedCharactersInternal.Any);
|
||||
}
|
||||
}
|
||||
|
@ -2245,11 +2273,12 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
private void ParseWhileClause(in SyntaxListBuilder<RazorSyntaxNode> builder)
|
||||
{
|
||||
SetAcceptedCharacters(AcceptedCharactersInternal.Any);
|
||||
var whitespace = SkipToNextImportantToken(builder);
|
||||
using var whitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
SkipToNextImportantToken(builder, ref whitespace.AsRef());
|
||||
|
||||
if (At(CSharpKeyword.While))
|
||||
{
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
Assert(CSharpKeyword.While);
|
||||
AcceptAndMoveNext();
|
||||
AcceptWhile(IsSpacingTokenIncludingNewLinesAndComments);
|
||||
|
@ -2261,7 +2290,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
else
|
||||
{
|
||||
PutCurrentBack();
|
||||
PutBack(whitespace);
|
||||
PutBack(in whitespace);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2271,14 +2300,15 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
var topLevel = transition != null;
|
||||
var block = new Block(CurrentToken, CurrentStart);
|
||||
var usingToken = EatCurrentToken();
|
||||
var whitespaceOrComments = ReadWhile(IsSpacingTokenIncludingComments);
|
||||
using var whitespaceOrComments = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(IsSpacingTokenIncludingComments, ref whitespaceOrComments.AsRef());
|
||||
var atLeftParen = At(SyntaxKind.LeftParenthesis);
|
||||
var atIdentifier = At(SyntaxKind.Identifier);
|
||||
var atStatic = At(CSharpKeyword.Static);
|
||||
|
||||
// Put the read tokens back and let them be handled later.
|
||||
PutCurrentBack();
|
||||
PutBack(whitespaceOrComments);
|
||||
PutBack(in whitespaceOrComments);
|
||||
PutBack(usingToken);
|
||||
EnsureCurrent();
|
||||
|
||||
|
@ -2374,11 +2404,12 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
// non-static using
|
||||
nonNamespaceTokenCount = TokenBuilder.Count;
|
||||
TryParseNamespaceOrTypeName(directiveBuilder);
|
||||
var whitespace = ReadWhile(IsSpacingTokenIncludingNewLinesAndComments);
|
||||
using var whitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(IsSpacingTokenIncludingNewLinesAndComments, ref whitespace.AsRef());
|
||||
if (At(SyntaxKind.Assign))
|
||||
{
|
||||
// Alias
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
Assert(SyntaxKind.Assign);
|
||||
AcceptAndMoveNext();
|
||||
|
||||
|
@ -2390,7 +2421,7 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
else
|
||||
{
|
||||
PutCurrentBack();
|
||||
PutBack(whitespace);
|
||||
PutBack(in whitespace);
|
||||
}
|
||||
}
|
||||
else if (At(CSharpKeyword.Static))
|
||||
|
@ -2564,17 +2595,18 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
!Context.DesignTimeMode &&
|
||||
!IsNested)
|
||||
{
|
||||
var whitespace = ReadWhile(static token => token.Kind == SyntaxKind.Whitespace);
|
||||
using var whitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(static token => token.Kind == SyntaxKind.Whitespace, ref whitespace.AsRef());
|
||||
if (At(SyntaxKind.NewLine))
|
||||
{
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
AcceptAndMoveNext();
|
||||
PutCurrentBack();
|
||||
}
|
||||
else
|
||||
{
|
||||
PutCurrentBack();
|
||||
PutBack(whitespace);
|
||||
PutBack(in whitespace);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -2583,14 +2615,18 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
}
|
||||
|
||||
private IReadOnlyList<SyntaxToken> SkipToNextImportantToken(in SyntaxListBuilder<RazorSyntaxNode> builder)
|
||||
private void SkipToNextImportantToken(
|
||||
in SyntaxListBuilder<RazorSyntaxNode> builder,
|
||||
ref PooledArrayBuilder<SyntaxToken> whitespace)
|
||||
{
|
||||
Debug.Assert(whitespace.Count == 0, "Expected empty builder.");
|
||||
|
||||
while (!EndOfFile)
|
||||
{
|
||||
var whitespace = ReadWhile(IsSpacingTokenIncludingNewLinesAndComments);
|
||||
ReadWhile(IsSpacingTokenIncludingNewLinesAndComments, ref whitespace);
|
||||
if (At(SyntaxKind.RazorCommentTransition))
|
||||
{
|
||||
Accept(whitespace);
|
||||
Accept(in whitespace);
|
||||
SetAcceptedCharacters(AcceptedCharactersInternal.Any);
|
||||
AcceptMarkerTokenIfNecessary();
|
||||
builder.Add(OutputTokensAsStatementLiteral());
|
||||
|
@ -2599,10 +2635,11 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
else
|
||||
{
|
||||
return whitespace;
|
||||
return;
|
||||
}
|
||||
|
||||
whitespace.Clear();
|
||||
}
|
||||
return Array.Empty<SyntaxToken>();
|
||||
}
|
||||
|
||||
private void DefaultSpanContextConfig(SpanEditHandlerBuilder? editHandlerBuilder, ref ISpanChunkGenerator? generator)
|
||||
|
@ -2722,14 +2759,14 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
if (!EndOfFile &&
|
||||
!(stopAtEndOfLine && At(SyntaxKind.NewLine)))
|
||||
{
|
||||
var tokens = new List<SyntaxToken>();
|
||||
using var tokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
do
|
||||
{
|
||||
if (IsAtEmbeddedTransition(
|
||||
(mode & BalancingModes.AllowCommentsAndTemplates) == BalancingModes.AllowCommentsAndTemplates,
|
||||
(mode & BalancingModes.AllowEmbeddedTransitions) == BalancingModes.AllowEmbeddedTransitions))
|
||||
{
|
||||
Accept(tokens);
|
||||
Accept(in tokens);
|
||||
tokens.Clear();
|
||||
ParseEmbeddedTransition(builder);
|
||||
|
||||
|
@ -2768,13 +2805,13 @@ internal class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer>
|
|||
}
|
||||
else
|
||||
{
|
||||
Accept(tokens);
|
||||
Accept(in tokens);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Accept all the tokens we saw
|
||||
Accept(tokens);
|
||||
Accept(in tokens);
|
||||
}
|
||||
}
|
||||
return nesting == 0;
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Diagnostics;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
||||
|
@ -350,8 +351,10 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
((previousSpan is MarkupStartTagSyntax startTag && startTag.IsMarkupTransition) ||
|
||||
(previousSpan is MarkupEndTagSyntax endTag && endTag.IsMarkupTransition)))
|
||||
{
|
||||
var tokens = ReadWhile(
|
||||
static f => (f.Kind == SyntaxKind.Whitespace) || (f.Kind == SyntaxKind.NewLine));
|
||||
using var tokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(
|
||||
static f => (f.Kind == SyntaxKind.Whitespace) || (f.Kind == SyntaxKind.NewLine),
|
||||
ref tokens.AsRef());
|
||||
|
||||
// Make sure the current token is not markup, which can be html start tag or @:
|
||||
if (!(At(SyntaxKind.OpenAngle) ||
|
||||
|
@ -362,7 +365,7 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
}
|
||||
|
||||
PutCurrentBack();
|
||||
PutBack(tokens);
|
||||
PutBack(in tokens);
|
||||
EnsureCurrent();
|
||||
}
|
||||
if (shouldAcceptWhitespaceAndNewLine)
|
||||
|
@ -698,7 +701,7 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
var bookmark = CurrentStart.AbsoluteIndex;
|
||||
|
||||
// Skip whitespace
|
||||
ReadWhile(IsSpacingTokenIncludingNewLines);
|
||||
SkipWhile(IsSpacingTokenIncludingNewLines);
|
||||
|
||||
// Open Angle
|
||||
if (At(SyntaxKind.OpenAngle) && NextIs(SyntaxKind.ForwardSlash))
|
||||
|
@ -1070,21 +1073,25 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
|
||||
// http://dev.w3.org/html5/spec/tokenization.html#before-attribute-name-state
|
||||
// Capture whitespace
|
||||
var attributePrefixWhitespace = ReadWhile(static token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
|
||||
using var attributePrefixWhitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(
|
||||
static token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine,
|
||||
ref attributePrefixWhitespace.AsRef());
|
||||
|
||||
// http://dev.w3.org/html5/spec/tokenization.html#attribute-name-state
|
||||
// Read the 'name' (i.e. read until the '=' or whitespace/newline)
|
||||
if (!TryParseAttributeName(out var nameTokens))
|
||||
using var nameTokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
if (!TryParseAttributeName(ref nameTokens.AsRef()))
|
||||
{
|
||||
// Unexpected character in tag, enter recovery
|
||||
Accept(attributePrefixWhitespace);
|
||||
Accept(in attributePrefixWhitespace);
|
||||
ParseMiscAttribute(builder);
|
||||
return;
|
||||
}
|
||||
|
||||
Accept(attributePrefixWhitespace); // Whitespace before attribute name
|
||||
Accept(in attributePrefixWhitespace); // Whitespace before attribute name
|
||||
var namePrefix = OutputAsMarkupLiteral();
|
||||
Accept(nameTokens); // Attribute name
|
||||
Accept(in nameTokens); // Attribute name
|
||||
var name = OutputAsMarkupLiteralRequired();
|
||||
|
||||
var atMinimizedAttribute = !TokenExistsAfterWhitespace(SyntaxKind.Equals);
|
||||
|
@ -1102,9 +1109,8 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
}
|
||||
}
|
||||
|
||||
private bool TryParseAttributeName(out IReadOnlyList<SyntaxToken> nameTokens)
|
||||
private bool TryParseAttributeName(ref PooledArrayBuilder<SyntaxToken> nameTokens)
|
||||
{
|
||||
nameTokens = Array.Empty<SyntaxToken>();
|
||||
//
|
||||
// We are currently here <input |name="..." />
|
||||
// If we encounter a transition (@) here, it can be parsed as CSharp or Markup depending on the feature flag.
|
||||
|
@ -1119,7 +1125,7 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
|
||||
if (IsValidAttributeNameToken(CurrentToken))
|
||||
{
|
||||
nameTokens = ReadWhile(
|
||||
ReadWhile(
|
||||
static (token, self) =>
|
||||
token.Kind != SyntaxKind.Whitespace &&
|
||||
token.Kind != SyntaxKind.NewLine &&
|
||||
|
@ -1127,7 +1133,8 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
token.Kind != SyntaxKind.CloseAngle &&
|
||||
token.Kind != SyntaxKind.OpenAngle &&
|
||||
(token.Kind != SyntaxKind.ForwardSlash || !self.NextIs(SyntaxKind.CloseAngle)),
|
||||
this);
|
||||
this,
|
||||
ref nameTokens);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1144,12 +1151,15 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
Assert(SyntaxKind.Equals); // We should be at "="
|
||||
var equalsToken = EatCurrentToken();
|
||||
|
||||
var whitespaceAfterEquals = ReadWhile(static token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
|
||||
using var whitespaceAfterEquals = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(
|
||||
static token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine,
|
||||
ref whitespaceAfterEquals.AsRef());
|
||||
var quote = SyntaxKind.Marker;
|
||||
if (At(SyntaxKind.SingleQuote) || At(SyntaxKind.DoubleQuote))
|
||||
{
|
||||
// Found a quote, the whitespace belongs to this attribute.
|
||||
Accept(whitespaceAfterEquals);
|
||||
Accept(in whitespaceAfterEquals);
|
||||
quote = CurrentToken.Kind;
|
||||
AcceptAndMoveNext();
|
||||
}
|
||||
|
@ -1157,7 +1167,7 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
{
|
||||
// No quotes found after the whitespace. Put it back so that it can be parsed later.
|
||||
PutCurrentBack();
|
||||
PutBack(whitespaceAfterEquals);
|
||||
PutBack(in whitespaceAfterEquals);
|
||||
}
|
||||
|
||||
MarkupTextLiteralSyntax? valuePrefix = null;
|
||||
|
@ -1165,7 +1175,18 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
MarkupTextLiteralSyntax? valueSuffix = null;
|
||||
|
||||
// First, determine if this is a 'data-' attribute (since those can't use conditional attributes)
|
||||
var nameContent = string.Concat(name.LiteralTokens.Nodes.Select(s => s.Content));
|
||||
string nameContent;
|
||||
|
||||
using (StringBuilderPool.GetPooledObject(out var builder))
|
||||
{
|
||||
foreach (var node in name.LiteralTokens.Nodes)
|
||||
{
|
||||
builder.Append(node.Content);
|
||||
}
|
||||
|
||||
nameContent = builder.ToString();
|
||||
}
|
||||
|
||||
if (IsConditionalAttributeName(nameContent))
|
||||
{
|
||||
// We now have the value prefix which is usually whitespace and/or a quote
|
||||
|
@ -1238,7 +1259,10 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
private void ParseConditionalAttributeValue(in SyntaxListBuilder<RazorSyntaxNode> builder, SyntaxKind quote)
|
||||
{
|
||||
var prefixStart = CurrentStart;
|
||||
var prefixTokens = ReadWhile(static token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine);
|
||||
using var prefixTokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(
|
||||
static token => token.Kind == SyntaxKind.Whitespace || token.Kind == SyntaxKind.NewLine,
|
||||
ref prefixTokens.AsRef());
|
||||
|
||||
if (At(SyntaxKind.Transition))
|
||||
{
|
||||
|
@ -1247,11 +1271,24 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
using (var pooledResult = Pool.Allocate<RazorSyntaxNode>())
|
||||
{
|
||||
var markupBuilder = pooledResult.Builder;
|
||||
Accept(prefixTokens);
|
||||
Accept(in prefixTokens);
|
||||
|
||||
// Render a single "@" in place of "@@".
|
||||
string prefixContent;
|
||||
|
||||
using (var pooledBuilder = StringBuilderPool.GetPooledObject())
|
||||
{
|
||||
var prefixBuilder = pooledBuilder.Object;
|
||||
foreach (var prefixToken in prefixTokens)
|
||||
{
|
||||
prefixBuilder.Append(prefixToken.Content);
|
||||
}
|
||||
|
||||
prefixContent = prefixBuilder.ToString();
|
||||
}
|
||||
|
||||
chunkGenerator = new LiteralAttributeChunkGenerator(
|
||||
new LocationTagged<string>(string.Concat(prefixTokens.Select(s => s.Content)), prefixStart),
|
||||
new LocationTagged<string>(prefixContent, prefixStart),
|
||||
new LocationTagged<string>(CurrentToken.Content, CurrentStart));
|
||||
AcceptAndMoveNext();
|
||||
SetAcceptedCharacters(AcceptedCharactersInternal.None);
|
||||
|
@ -1268,7 +1305,7 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
}
|
||||
else
|
||||
{
|
||||
Accept(prefixTokens);
|
||||
Accept(in prefixTokens);
|
||||
var valueStart = CurrentStart;
|
||||
PutCurrentBack();
|
||||
|
||||
|
@ -1287,13 +1324,14 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
}
|
||||
else
|
||||
{
|
||||
Accept(prefixTokens);
|
||||
Accept(in prefixTokens);
|
||||
var prefix = OutputAsMarkupLiteral();
|
||||
|
||||
// Literal value
|
||||
// 'quote' should be "Unknown" if not quoted and tokens coming from the tokenizer should never have
|
||||
// "Unknown" type.
|
||||
var valueTokens = ReadWhile(
|
||||
using var valueTokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(
|
||||
static (token, arg) =>
|
||||
// These three conditions find separators which break the attribute value into portions
|
||||
token.Kind != SyntaxKind.Whitespace &&
|
||||
|
@ -1302,8 +1340,10 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
// This condition checks for the end of the attribute value (it repeats some of the checks above
|
||||
// but for now that's ok)
|
||||
!arg.self.IsEndOfAttributeValue(arg.quote, token),
|
||||
(self: this, quote));
|
||||
Accept(valueTokens);
|
||||
(self: this, quote),
|
||||
ref valueTokens.AsRef());
|
||||
Accept(in valueTokens);
|
||||
|
||||
var value = OutputAsMarkupLiteral();
|
||||
|
||||
var literalAttributeValue = SyntaxFactory.MarkupLiteralAttributeValue(prefix, value);
|
||||
|
@ -1740,12 +1780,12 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
return false;
|
||||
}
|
||||
|
||||
private IReadOnlyList<SyntaxToken> FastReadWhitespaceAndNewLines()
|
||||
private void FastReadWhitespaceAndNewLines(ref PooledArrayBuilder<SyntaxToken> whitespaceTokens)
|
||||
{
|
||||
Debug.Assert(whitespaceTokens.Count == 0, "Expected empty builder.");
|
||||
|
||||
if (EnsureCurrent() && (CurrentToken.Kind == SyntaxKind.Whitespace || CurrentToken.Kind == SyntaxKind.NewLine))
|
||||
{
|
||||
var whitespaceTokens = new List<SyntaxToken>();
|
||||
|
||||
whitespaceTokens.Add(CurrentToken);
|
||||
NextToken();
|
||||
|
||||
|
@ -1754,16 +1794,13 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
whitespaceTokens.Add(CurrentToken);
|
||||
NextToken();
|
||||
}
|
||||
|
||||
return whitespaceTokens;
|
||||
}
|
||||
|
||||
return Array.Empty<SyntaxToken>();
|
||||
}
|
||||
|
||||
private ParserState GetParserState(ParseMode mode)
|
||||
{
|
||||
var whitespace = FastReadWhitespaceAndNewLines();
|
||||
using var whitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
FastReadWhitespaceAndNewLines(ref whitespace.AsRef());
|
||||
try
|
||||
{
|
||||
if (whitespace.Count == 0 && EndOfFile)
|
||||
|
@ -1838,7 +1875,7 @@ internal class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer>
|
|||
if (whitespace.Count > 0)
|
||||
{
|
||||
PutCurrentBack();
|
||||
PutBack(whitespace);
|
||||
PutBack(in whitespace);
|
||||
EnsureCurrent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.Legacy;
|
||||
|
||||
|
@ -100,17 +101,19 @@ internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
|||
return CurrentToken;
|
||||
}
|
||||
|
||||
using var _ = ListPool<SyntaxToken>.GetPooledObject(out var tokens);
|
||||
|
||||
// We add 1 in order to store the current token.
|
||||
var tokens = new SyntaxToken[count + 1];
|
||||
tokens.SetCapacityIfLarger(count + 1);
|
||||
var currentToken = CurrentToken;
|
||||
|
||||
tokens[0] = currentToken;
|
||||
tokens.Add(currentToken);
|
||||
|
||||
// We need to look forward "count" many times.
|
||||
for (var i = 1; i <= count; i++)
|
||||
{
|
||||
NextToken();
|
||||
tokens[i] = CurrentToken;
|
||||
tokens.Add(CurrentToken);
|
||||
}
|
||||
|
||||
// Restore Tokenizer's location to where it was pointing before the look-ahead.
|
||||
|
@ -141,7 +144,7 @@ internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
|||
|
||||
var matchFound = false;
|
||||
|
||||
var tokens = new List<SyntaxToken>();
|
||||
using var _ = ListPool<SyntaxToken>.GetPooledObject(out var tokens);
|
||||
tokens.Add(CurrentToken);
|
||||
|
||||
while (true)
|
||||
|
@ -204,15 +207,7 @@ internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
|||
/// that is the correct format for providing to this method. The caller of this method would,
|
||||
/// in that case, want to put c, b and a back into the stream, so "a, b, c" is the CORRECT order
|
||||
/// </remarks>
|
||||
protected internal void PutBack(IEnumerable<SyntaxToken> tokens)
|
||||
{
|
||||
foreach (var token in tokens.Reverse())
|
||||
{
|
||||
PutBack(token);
|
||||
}
|
||||
}
|
||||
|
||||
protected internal void PutBack(IReadOnlyList<SyntaxToken> tokens)
|
||||
protected void PutBack(ref readonly PooledArrayBuilder<SyntaxToken> tokens)
|
||||
{
|
||||
for (int i = tokens.Count - 1; i >= 0; i--)
|
||||
{
|
||||
|
@ -279,14 +274,16 @@ internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
|||
protected bool TokenExistsAfterWhitespace(SyntaxKind kind, bool includeNewLines = true)
|
||||
{
|
||||
var tokenFound = false;
|
||||
var whitespace = ReadWhile(
|
||||
using var whitespace = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(
|
||||
static (token, includeNewLines) =>
|
||||
token.Kind == SyntaxKind.Whitespace || (includeNewLines && token.Kind == SyntaxKind.NewLine),
|
||||
includeNewLines);
|
||||
includeNewLines,
|
||||
ref whitespace.AsRef());
|
||||
tokenFound = At(kind);
|
||||
|
||||
PutCurrentBack();
|
||||
PutBack(whitespace);
|
||||
PutBack(in whitespace);
|
||||
EnsureCurrent();
|
||||
|
||||
return tokenFound;
|
||||
|
@ -302,25 +299,57 @@ internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
|||
return true;
|
||||
}
|
||||
|
||||
protected internal IReadOnlyList<SyntaxToken> ReadWhile(Func<SyntaxToken, bool> condition)
|
||||
=> ReadWhile(static (token, condition) => condition(token), condition);
|
||||
|
||||
protected internal IReadOnlyList<SyntaxToken> ReadWhile<TArg>(Func<SyntaxToken, TArg, bool> condition, TArg arg)
|
||||
protected void ReadWhile<TArg>(
|
||||
Func<SyntaxToken, TArg, bool> predicate,
|
||||
TArg arg,
|
||||
ref PooledArrayBuilder<SyntaxToken> result)
|
||||
{
|
||||
if (!EnsureCurrent() || !condition(CurrentToken, arg))
|
||||
Debug.Assert(result.Count == 0, "Expected empty builder.");
|
||||
|
||||
if (!EnsureCurrent() || !predicate(CurrentToken, arg))
|
||||
{
|
||||
return Array.Empty<SyntaxToken>();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = new List<SyntaxToken>();
|
||||
do
|
||||
{
|
||||
result.Add(CurrentToken);
|
||||
NextToken();
|
||||
}
|
||||
while (EnsureCurrent() && condition(CurrentToken, arg));
|
||||
while (EnsureCurrent() && predicate(CurrentToken, arg));
|
||||
}
|
||||
|
||||
return result;
|
||||
protected void ReadWhile(
|
||||
Func<SyntaxToken, bool> predicate,
|
||||
ref PooledArrayBuilder<SyntaxToken> result)
|
||||
{
|
||||
Debug.Assert(result.Count == 0, "Expected empty builder.");
|
||||
|
||||
if (!EnsureCurrent() || !predicate(CurrentToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
result.Add(CurrentToken);
|
||||
NextToken();
|
||||
}
|
||||
while (EnsureCurrent() && predicate(CurrentToken));
|
||||
}
|
||||
|
||||
protected void SkipWhile(Func<SyntaxToken, bool> predicate)
|
||||
{
|
||||
if (!EnsureCurrent() || !predicate(CurrentToken))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
NextToken();
|
||||
}
|
||||
while (EnsureCurrent() && predicate(CurrentToken));
|
||||
}
|
||||
|
||||
protected bool AtIdentifier(bool allowKeywords)
|
||||
|
@ -471,21 +500,24 @@ internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
|
|||
AcceptWhile(static (token, types) => types.All(expected => expected != token.Kind), types);
|
||||
}
|
||||
|
||||
protected internal void AcceptWhile(Func<SyntaxToken, bool> condition)
|
||||
protected void AcceptWhile(Func<SyntaxToken, bool> condition)
|
||||
{
|
||||
Accept(ReadWhile(condition));
|
||||
using var tokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(condition, ref tokens.AsRef());
|
||||
Accept(in tokens);
|
||||
}
|
||||
|
||||
protected internal void AcceptWhile<TArg>(Func<SyntaxToken, TArg, bool> condition, TArg arg)
|
||||
protected void AcceptWhile<TArg>(Func<SyntaxToken, TArg, bool> condition, TArg arg)
|
||||
{
|
||||
Accept(ReadWhile(condition, arg));
|
||||
using var tokens = new PooledArrayBuilder<SyntaxToken>();
|
||||
ReadWhile(condition, arg, ref tokens.AsRef());
|
||||
Accept(in tokens);
|
||||
}
|
||||
|
||||
protected internal void Accept(IReadOnlyList<SyntaxToken> tokens)
|
||||
protected void Accept(ref readonly PooledArrayBuilder<SyntaxToken> tokens)
|
||||
{
|
||||
for (int i = 0; i < tokens.Count; i++)
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var token = tokens[i];
|
||||
Accept(token);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor;
|
||||
|
@ -30,5 +29,8 @@ internal static class ListExtensions
|
|||
public static T[] ToArrayOrEmpty<T>(this List<T>? list)
|
||||
=> list?.Count > 0
|
||||
? list.ToArray()
|
||||
: Array.Empty<T>();
|
||||
: [];
|
||||
|
||||
public static bool Any<T>(this List<T> list)
|
||||
=> list.Count > 0;
|
||||
}
|
||||
|
|
|
@ -434,6 +434,8 @@ internal partial struct PooledArrayBuilder<T> : IDisposable
|
|||
};
|
||||
}
|
||||
|
||||
public bool Any() => Count > 0;
|
||||
|
||||
public void Push(T item)
|
||||
{
|
||||
Add(item);
|
||||
|
|
Загрузка…
Ссылка в новой задаче