-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathSyntaxFacts.cs
195 lines (183 loc) · 9.28 KB
/
SyntaxFacts.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
using System;
using System.Diagnostics;
namespace Draco.Compiler.Api.Syntax;
/// <summary>
/// Utilities for syntax.
/// </summary>
public static partial class SyntaxFacts
{
/// <summary>
/// Attempts to retrieve a user-friendly name for a <see cref="TokenKind"/>.
/// </summary>
/// <param name="tokenKind">The <see cref="TokenKind"/> to get the user-friendly name for.</param>
/// <returns>The user-friendly name of <paramref name="tokenKind"/>.</returns>
public static string GetUserFriendlyName(TokenKind tokenKind) => tokenKind switch
{
TokenKind.EndOfInput => "end of file",
TokenKind.LineStringEnd or TokenKind.MultiLineStringEnd => "end of string literal",
_ => GetTokenText(tokenKind) ?? tokenKind.ToString().ToLower(),
};
/// <summary>
/// Checks, if the given operator <see cref="TokenKind"/> is a compound assignment.
/// </summary>
/// <param name="tokenKind">The <see cref="TokenKind"/> to check.</param>
/// <param name="nonCompoundKind">The non-compound operator equivalent is written here, in case it is a compound
/// operator.</param>
/// <returns>True, if <paramref name="tokenKind"/> is a compound assignment, false otherwise.</returns>
public static bool TryGetOperatorOfCompoundAssignment(TokenKind tokenKind, out TokenKind nonCompoundKind)
{
switch (tokenKind)
{
case TokenKind.PlusAssign:
nonCompoundKind = TokenKind.Plus;
return true;
case TokenKind.MinusAssign:
nonCompoundKind = TokenKind.Minus;
return true;
case TokenKind.StarAssign:
nonCompoundKind = TokenKind.Star;
return true;
case TokenKind.SlashAssign:
nonCompoundKind = TokenKind.Slash;
return true;
default:
nonCompoundKind = default;
return false;
}
}
/// <summary>
/// Checks, if a given <see cref="TokenKind"/> corresponds to a compound assignment operator.
/// </summary>
/// <param name="tokenKind">The <see cref="TokenKind"/> to check.</param>
/// <returns>True, if <paramref name="tokenKind"/> represents a compound assignment operator, false otherwise.</returns>
public static bool IsCompoundAssignmentOperator(TokenKind tokenKind) =>
TryGetOperatorOfCompoundAssignment(tokenKind, out _);
/// <summary>
/// Checks, if a given <see cref="TokenKind"/> corresponds to a relational operator.
/// </summary>
/// <param name="tokenKind">The <see cref="TokenKind"/> to check.</param>
/// <returns>True, if <paramref name="tokenKind"/> is a relational operator, false otherwise.</returns>
public static bool IsRelationalOperator(TokenKind tokenKind) => tokenKind
is TokenKind.Equal
or TokenKind.NotEqual
or TokenKind.GreaterThan
or TokenKind.LessThan
or TokenKind.GreaterEqual
or TokenKind.LessEqual;
/// <summary>
/// Checks, if a given <see cref="TokenKind"/> represents a keyword.
/// </summary>
/// <param name="tokenKind">The <see cref="TokenKind"/> to check.</param>
/// <returns>True, if <paramref name="tokenKind"/> is a keyword, false otherwise.</returns>
public static bool IsKeyword(TokenKind tokenKind) =>
tokenKind.ToString().StartsWith("Keyword");
/// <summary>
/// Returns the replacement token for a given C-heritage token.
/// </summary>
/// <param name="tokenKind">The <see cref="TokenKind"/> of the heritage token.</param>
/// <returns>The syntactically valid token which replaces the <paramref name="tokenKind"/> heritage token, or null if <paramref name="tokenKind"/> is not a heritage token.</returns>
public static TokenKind? GetHeritageReplacement(TokenKind tokenKind) => tokenKind switch
{
TokenKind.CMod => TokenKind.KeywordMod,
TokenKind.COr => TokenKind.KeywordOr,
TokenKind.CAnd => TokenKind.KeywordAnd,
TokenKind.CNot => TokenKind.KeywordNot,
_ => null
};
/// <summary>
/// Computes the cutoff sequence that is removed from each line of a multiline string.
/// </summary>
/// <param name="str">The string syntax to compute the cutoff for.</param>
/// <returns>The cutoff string for <paramref name="str"/>.</returns>
public static string ComputeCutoff(StringExpressionSyntax str) => ComputeCutoff(str.Green);
/// <summary>
/// See <see cref="ComputeCutoff(StringExpressionSyntax)"/>.
/// </summary>
internal static string ComputeCutoff(Internal.Syntax.StringExpressionSyntax str)
{
// Line strings have no cutoff
if (str.OpenQuotes.Kind == TokenKind.LineStringStart) return string.Empty;
// Multiline strings
Debug.Assert(str.CloseQuotes.LeadingTrivia.Count <= 2);
// If this is true, we have malformed input
if (str.CloseQuotes.LeadingTrivia.Count == 0) return string.Empty;
// If this is true, there's only newline, no spaces before
if (str.CloseQuotes.LeadingTrivia.Count == 1) return string.Empty;
// The first trivia was newline, the second must be spaces
Debug.Assert(str.CloseQuotes.LeadingTrivia[1].Kind == TriviaKind.Whitespace);
return str.CloseQuotes.LeadingTrivia[1].Text;
}
/// <summary>
/// Extracts the import path from an <see cref="ImportPathSyntax"/> as a string.
/// </summary>
/// <param name="path">The path to extract.</param>
/// <returns>The string representation of <paramref name="path"/>.</returns>
public static string ImportPathToString(ImportPathSyntax path) => path switch
{
RootImportPathSyntax root => root.Name.Text,
MemberImportPathSyntax member => $"{ImportPathToString(member.Accessed)}.{member.Member.Text}",
_ => throw new ArgumentOutOfRangeException(nameof(path)),
};
/// <summary>
/// Checks, if the given node is a complete entry.
/// </summary>
/// <param name="node">The node to check.</param>
/// <returns>True, if <paramref name="node"/> is a complete entry, false otherwise.</returns>
public static bool IsCompleteEntry(SyntaxNode? node)
{
static bool IsMissing(SyntaxNode? node) => node switch
{
SyntaxToken t => t.Kind != TokenKind.EndOfInput && t.Text.Length == 0,
UnexpectedDeclarationSyntax d => d.Nodes.Count == 0,
UnexpectedFunctionBodySyntax b => b.Nodes.Count == 0,
UnexpectedTypeSyntax t => t.Nodes.Count == 0,
UnexpectedStatementSyntax s => s.Nodes.Count == 0,
UnexpectedExpressionSyntax e => e.Nodes.Count == 0,
UnexpectedStringPartSyntax p => p.Nodes.Count == 0,
_ => false,
};
if (node is null) return true;
return node switch
{
_ when IsMissing(node) => false,
CompilationUnitSyntax cu => !IsMissing(cu.Declarations[^1]),
GenericParameterListSyntax gpl => !IsMissing(gpl.CloseBracket),
ModuleDeclarationSyntax md => !IsMissing(md.CloseBrace),
ImportDeclarationSyntax id => !IsMissing(id.Semicolon),
FunctionDeclarationSyntax fd => IsCompleteEntry(fd.Body),
NormalParameterSyntax p => IsCompleteEntry(p.Type),
BlockFunctionBodySyntax bfb => !IsMissing(bfb.CloseBrace),
InlineFunctionBodySyntax ifb => !IsMissing(ifb.Semicolon),
LabelDeclarationSyntax ld => !IsMissing(ld.Colon),
VariableDeclarationSyntax vd => !IsMissing(vd.Semicolon),
NameTypeSyntax nt => !IsMissing(nt.Name),
MemberTypeSyntax mt => !IsMissing(mt.Member),
GenericTypeSyntax gt => !IsMissing(gt.CloseBracket),
DeclarationStatementSyntax ds => IsCompleteEntry(ds.Declaration),
ExpressionStatementSyntax es => IsCompleteEntry(es.Expression) && !IsMissing(es.Semicolon),
StatementExpressionSyntax se => IsCompleteEntry(se.Statement),
BlockExpressionSyntax be => !IsMissing(be.CloseBrace),
IfExpressionSyntax ie => IsCompleteEntry(ie.Then) && IsCompleteEntry(ie.Else),
WhileExpressionSyntax we => IsCompleteEntry(we.Then),
ForExpressionSyntax fe => IsCompleteEntry(fe.Then),
GotoExpressionSyntax ge => IsCompleteEntry(ge.Target),
ReturnExpressionSyntax re => IsCompleteEntry(re.Value),
LiteralExpressionSyntax le => !IsMissing(le.Literal),
CallExpressionSyntax ce => !IsMissing(ce.CloseParen),
IndexExpressionSyntax ie => !IsMissing(ie.CloseBracket),
GenericExpressionSyntax ge => !IsMissing(ge.CloseBracket),
NameExpressionSyntax ne => !IsMissing(ne.Name),
MemberExpressionSyntax me => !IsMissing(me.Member),
UnaryExpressionSyntax ue => IsCompleteEntry(ue.Operand),
BinaryExpressionSyntax be => IsCompleteEntry(be.Right),
RelationalExpressionSyntax re => IsCompleteEntry(re.Comparisons[^1].Right),
GroupingExpressionSyntax ge => !IsMissing(ge.CloseParen),
StringExpressionSyntax se => !IsMissing(se.CloseQuotes),
NameLabelSyntax nl => !IsMissing(nl.Name),
ScriptEntrySyntax se => se.Value is null
? se.Statements.Count == 0 || IsCompleteEntry(se.Statements[^1])
: IsCompleteEntry(se.Value),
_ => true,
};
}
}