1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Diagnostics.CodeAnalysis;
6 using System.Linq;
7 using System.Web.Razor.Editor;
8 using System.Web.Razor.Generator;
9 using System.Web.Razor.Parser.SyntaxTree;
10 using System.Web.Razor.Resources;
11 using System.Web.Razor.Text;
12 using System.Web.Razor.Tokenizer;
13 using System.Web.Razor.Tokenizer.Symbols;
14 
15 namespace System.Web.Razor.Parser
16 {
17     public partial class VBCodeParser : TokenizerBackedParser<VBTokenizer, VBSymbol, VBSymbolType>
18     {
19         internal static ISet<string> DefaultKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
20         {
21             "functions",
22             "code",
23             "section",
24             "do",
25             "while",
26             "if",
27             "select",
28             "for",
29             "try",
30             "with",
31             "synclock",
32             "using",
33             "imports",
34             "inherits",
35             "option",
36             "helper",
37             "namespace",
38             "class",
39             "layout",
40             "sessionstate"
41         };
42 
43         private Dictionary<VBKeyword, Func<bool>> _keywordHandlers = new Dictionary<VBKeyword, Func<bool>>();
44         private Dictionary<string, Func<bool>> _directiveHandlers = new Dictionary<string, Func<bool>>(StringComparer.OrdinalIgnoreCase);
45 
46         [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Necessary state is initialized before calling virtual methods")]
VBCodeParser()47         public VBCodeParser()
48         {
49             DirectParentIsCode = false;
50             Keywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
51             SetUpKeywords();
52             SetUpDirectives();
53         }
54 
55         protected internal ISet<string> Keywords { get; private set; }
56 
57         protected override LanguageCharacteristics<VBTokenizer, VBSymbol, VBSymbolType> Language
58         {
59             get { return VBLanguageCharacteristics.Instance; }
60         }
61 
62         protected override ParserBase OtherParser
63         {
64             get { return Context.MarkupParser; }
65         }
66 
67         private bool IsNested { get; set; }
68         private bool DirectParentIsCode { get; set; }
69 
IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)70         protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
71         {
72             return (allowTransitions && Language.IsTransition(CurrentSymbol) && !Was(VBSymbolType.Dot)) ||
73                    (allowTemplatesAndComments && Language.IsCommentStart(CurrentSymbol)) ||
74                    (Language.IsTransition(CurrentSymbol) && NextIs(VBSymbolType.Transition));
75         }
76 
HandleEmbeddedTransition()77         protected override void HandleEmbeddedTransition()
78         {
79             HandleEmbeddedTransition(null);
80         }
81 
HandleEmbeddedTransition(VBSymbol lastWhiteSpace)82         protected void HandleEmbeddedTransition(VBSymbol lastWhiteSpace)
83         {
84             if (At(VBSymbolType.RazorCommentTransition))
85             {
86                 Accept(lastWhiteSpace);
87                 RazorComment();
88             }
89             else if ((At(VBSymbolType.Transition) && !Was(VBSymbolType.Dot)))
90             {
91                 HandleTransition(lastWhiteSpace);
92             }
93         }
94 
ParseBlock()95         public override void ParseBlock()
96         {
97             if (Context == null)
98             {
99                 throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
100             }
101             using (PushSpanConfig())
102             {
103                 if (Context == null)
104                 {
105                     throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
106                 }
107 
108                 Initialize(Span);
109                 NextToken();
110                 using (Context.StartBlock())
111                 {
112                     IEnumerable<VBSymbol> syms = ReadWhile(sym => sym.Type == VBSymbolType.WhiteSpace);
113                     if (At(VBSymbolType.Transition))
114                     {
115                         Accept(syms);
116                         Span.CodeGenerator = new StatementCodeGenerator();
117                         Output(SpanKind.Code);
118                     }
119                     else
120                     {
121                         PutBack(syms);
122                         EnsureCurrent();
123                     }
124 
125                     // Allow a transition span, but don't require it
126                     if (Optional(VBSymbolType.Transition))
127                     {
128                         Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
129                         Span.CodeGenerator = SpanCodeGenerator.Null;
130                         Output(SpanKind.Transition);
131                     }
132 
133                     Context.CurrentBlock.Type = BlockType.Expression;
134                     Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
135 
136                     // Determine the type of the block
137                     bool isComplete = false;
138                     Action<SpanBuilder> config = null;
139                     if (!EndOfFile)
140                     {
141                         switch (CurrentSymbol.Type)
142                         {
143                             case VBSymbolType.Identifier:
144                                 if (!TryDirectiveBlock(ref isComplete))
145                                 {
146                                     ImplicitExpression();
147                                 }
148                                 break;
149                             case VBSymbolType.LeftParenthesis:
150                                 isComplete = ExplicitExpression();
151                                 break;
152                             case VBSymbolType.Keyword:
153                                 Context.CurrentBlock.Type = BlockType.Statement;
154                                 Context.CurrentBlock.CodeGenerator = BlockCodeGenerator.Null;
155                                 isComplete = KeywordBlock();
156                                 break;
157                             case VBSymbolType.WhiteSpace:
158                             case VBSymbolType.NewLine:
159                                 config = ImplictExpressionSpanConfig;
160                                 Context.OnError(CurrentLocation,
161                                                 RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_VB);
162                                 break;
163                             default:
164                                 config = ImplictExpressionSpanConfig;
165                                 Context.OnError(CurrentLocation,
166                                                 RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_VB,
167                                                 CurrentSymbol.Content);
168                                 break;
169                         }
170                     }
171                     else
172                     {
173                         config = ImplictExpressionSpanConfig;
174                         Context.OnError(CurrentLocation,
175                                         RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock);
176                     }
177                     using (PushSpanConfig(config))
178                     {
179                         if (!isComplete && Span.Symbols.Count == 0 && Context.LastAcceptedCharacters != AcceptedCharacters.Any)
180                         {
181                             AddMarkerSymbolIfNecessary();
182                         }
183                         Output(SpanKind.Code);
184                         PutCurrentBack();
185                     }
186                 }
187             }
188         }
189 
ImplictExpressionSpanConfig(SpanBuilder span)190         private void ImplictExpressionSpanConfig(SpanBuilder span)
191         {
192             span.CodeGenerator = new ExpressionCodeGenerator();
193             span.EditHandler = new ImplicitExpressionEditHandler(
194                 Language.TokenizeString,
195                 Keywords,
196                 acceptTrailingDot: DirectParentIsCode)
197             {
198                 AcceptedCharacters = AcceptedCharacters.NonWhiteSpace
199             };
200         }
201 
StatementBlockSpanConfiguration(SpanCodeGenerator codeGenerator)202         private Action<SpanBuilder> StatementBlockSpanConfiguration(SpanCodeGenerator codeGenerator)
203         {
204             return span =>
205             {
206                 span.Kind = SpanKind.Code;
207                 span.CodeGenerator = codeGenerator;
208                 span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
209             };
210         }
211 
212         // Pass "complete" flag by ref, not out because some paths may not change it.
TryDirectiveBlock(ref bool complete)213         private bool TryDirectiveBlock(ref bool complete)
214         {
215             Assert(VBSymbolType.Identifier);
216             Func<bool> handler;
217             if (_directiveHandlers.TryGetValue(CurrentSymbol.Content, out handler))
218             {
219                 Context.CurrentBlock.CodeGenerator = BlockCodeGenerator.Null;
220                 complete = handler();
221                 return true;
222             }
223             return false;
224         }
225 
KeywordBlock()226         private bool KeywordBlock()
227         {
228             Assert(VBSymbolType.Keyword);
229             Func<bool> handler;
230             if (_keywordHandlers.TryGetValue(CurrentSymbol.Keyword.Value, out handler))
231             {
232                 Span.CodeGenerator = new StatementCodeGenerator();
233                 Context.CurrentBlock.Type = BlockType.Statement;
234                 return handler();
235             }
236             else
237             {
238                 ImplicitExpression();
239                 return false;
240             }
241         }
242 
ExplicitExpression()243         private bool ExplicitExpression()
244         {
245             Context.CurrentBlock.Type = BlockType.Expression;
246             Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
247             SourceLocation start = CurrentLocation;
248             Expected(VBSymbolType.LeftParenthesis);
249             Span.CodeGenerator = SpanCodeGenerator.Null;
250             Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
251             Output(SpanKind.MetaCode);
252 
253             Span.CodeGenerator = new ExpressionCodeGenerator();
254             using (PushSpanConfig(span => span.CodeGenerator = new ExpressionCodeGenerator()))
255             {
256                 if (!Balance(BalancingModes.NoErrorOnFailure |
257                              BalancingModes.BacktrackOnFailure |
258                              BalancingModes.AllowCommentsAndTemplates,
259                              VBSymbolType.LeftParenthesis,
260                              VBSymbolType.RightParenthesis,
261                              start))
262                 {
263                     Context.OnError(start,
264                                     RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
265                                     RazorResources.BlockName_ExplicitExpression,
266                                     VBSymbol.GetSample(VBSymbolType.RightParenthesis),
267                                     VBSymbol.GetSample(VBSymbolType.LeftParenthesis));
268                     AcceptUntil(VBSymbolType.NewLine);
269                     AddMarkerSymbolIfNecessary();
270                     Output(SpanKind.Code);
271                     PutCurrentBack();
272                     return false;
273                 }
274                 else
275                 {
276                     AddMarkerSymbolIfNecessary();
277                     Output(SpanKind.Code);
278                     Expected(VBSymbolType.RightParenthesis);
279                     Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
280                     Span.CodeGenerator = SpanCodeGenerator.Null;
281                     Output(SpanKind.MetaCode);
282                     PutCurrentBack();
283                     return true;
284                 }
285             }
286         }
287 
ImplicitExpression()288         private void ImplicitExpression()
289         {
290             Context.CurrentBlock.Type = BlockType.Expression;
291             Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
292             using (PushSpanConfig(ImplictExpressionSpanConfig))
293             {
294                 Expected(VBSymbolType.Identifier, VBSymbolType.Keyword);
295                 Span.CodeGenerator = new ExpressionCodeGenerator();
296                 while (!EndOfFile)
297                 {
298                     switch (CurrentSymbol.Type)
299                     {
300                         case VBSymbolType.LeftParenthesis:
301                             SourceLocation start = CurrentLocation;
302                             AcceptAndMoveNext();
303 
304                             Action<SpanBuilder> oldConfig = SpanConfig;
305                             using (PushSpanConfig())
306                             {
307                                 ConfigureSpan(span =>
308                                 {
309                                     oldConfig(span);
310                                     span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
311                                 });
312                                 Balance(BalancingModes.AllowCommentsAndTemplates,
313                                         VBSymbolType.LeftParenthesis,
314                                         VBSymbolType.RightParenthesis,
315                                         start);
316                             }
317                             if (Optional(VBSymbolType.RightParenthesis))
318                             {
319                                 Span.EditHandler.AcceptedCharacters = AcceptedCharacters.NonWhiteSpace;
320                             }
321                             break;
322                         case VBSymbolType.Dot:
323                             VBSymbol dot = CurrentSymbol;
324                             NextToken();
325                             if (At(VBSymbolType.Identifier) || At(VBSymbolType.Keyword))
326                             {
327                                 Accept(dot);
328                                 AcceptAndMoveNext();
329                             }
330                             else if (At(VBSymbolType.Transition))
331                             {
332                                 VBSymbol at = CurrentSymbol;
333                                 NextToken();
334                                 if (At(VBSymbolType.Identifier) || At(VBSymbolType.Keyword))
335                                 {
336                                     Accept(dot);
337                                     Accept(at);
338                                     AcceptAndMoveNext();
339                                 }
340                                 else
341                                 {
342                                     PutBack(at);
343                                     PutBack(dot);
344                                 }
345                             }
346                             else
347                             {
348                                 PutCurrentBack();
349                                 if (IsNested)
350                                 {
351                                     Accept(dot);
352                                 }
353                                 else
354                                 {
355                                     PutBack(dot);
356                                 }
357                                 return;
358                             }
359                             break;
360                         default:
361                             PutCurrentBack();
362                             return;
363                     }
364                 }
365             }
366         }
367 
MapKeyword(VBKeyword keyword, Func<bool> action)368         protected void MapKeyword(VBKeyword keyword, Func<bool> action)
369         {
370             _keywordHandlers[keyword] = action;
371             Keywords.Add(keyword.ToString());
372         }
373 
MapDirective(string directive, Func<bool> action)374         protected void MapDirective(string directive, Func<bool> action)
375         {
376             _directiveHandlers[directive] = action;
377             Keywords.Add(directive);
378         }
379 
380         [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This only occurs in Release builds, where this method is empty by design")]
381         [Conditional("DEBUG")]
Assert(VBKeyword keyword)382         protected void Assert(VBKeyword keyword)
383         {
384             Debug.Assert(CurrentSymbol.Type == VBSymbolType.Keyword && CurrentSymbol.Keyword == keyword);
385         }
386 
At(VBKeyword keyword)387         protected bool At(VBKeyword keyword)
388         {
389             return At(VBSymbolType.Keyword) && CurrentSymbol.Keyword == keyword;
390         }
391 
OtherParserBlock()392         protected void OtherParserBlock()
393         {
394             OtherParserBlock(null, null);
395         }
396 
OtherParserBlock(string startSequence, string endSequence)397         protected void OtherParserBlock(string startSequence, string endSequence)
398         {
399             using (PushSpanConfig())
400             {
401                 if (Span.Symbols.Count > 0)
402                 {
403                     Output(SpanKind.Code);
404                 }
405 
406                 Context.SwitchActiveParser();
407 
408                 bool old = DirectParentIsCode;
409                 DirectParentIsCode = false;
410 
411                 Debug.Assert(ReferenceEquals(Context.ActiveParser, Context.MarkupParser));
412                 if (!String.IsNullOrEmpty(startSequence) || !String.IsNullOrEmpty(endSequence))
413                 {
414                     Context.MarkupParser.ParseSection(Tuple.Create(startSequence, endSequence), false);
415                 }
416                 else
417                 {
418                     Context.MarkupParser.ParseBlock();
419                 }
420 
421                 DirectParentIsCode = old;
422 
423                 Context.SwitchActiveParser();
424                 EnsureCurrent();
425             }
426             Initialize(Span);
427         }
428 
HandleTransition(VBSymbol lastWhiteSpace)429         protected void HandleTransition(VBSymbol lastWhiteSpace)
430         {
431             if (At(VBSymbolType.RazorCommentTransition))
432             {
433                 Accept(lastWhiteSpace);
434                 RazorComment();
435                 return;
436             }
437 
438             // Check the next character
439             VBSymbol transition = CurrentSymbol;
440             NextToken();
441             if (At(VBSymbolType.LessThan) || At(VBSymbolType.Colon))
442             {
443                 // Put the transition back
444                 PutCurrentBack();
445                 PutBack(transition);
446 
447                 // If we're in design-time mode, accept the whitespace, otherwise put it back
448                 if (Context.DesignTimeMode)
449                 {
450                     Accept(lastWhiteSpace);
451                 }
452                 else
453                 {
454                     PutBack(lastWhiteSpace);
455                 }
456 
457                 // Switch to markup
458                 OtherParserBlock();
459             }
460             else if (At(VBSymbolType.Transition))
461             {
462                 if (Context.IsWithin(BlockType.Template))
463                 {
464                     Context.OnError(transition.Start, RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested);
465                 }
466                 Accept(lastWhiteSpace);
467                 VBSymbol transition2 = CurrentSymbol;
468                 NextToken();
469                 if (At(VBSymbolType.LessThan) || At(VBSymbolType.Colon))
470                 {
471                     PutCurrentBack();
472                     PutBack(transition2);
473                     PutBack(transition);
474                     Output(SpanKind.Code);
475 
476                     // Start a template block and switch to Markup
477                     using (Context.StartBlock(BlockType.Template))
478                     {
479                         Context.CurrentBlock.CodeGenerator = new TemplateBlockCodeGenerator();
480                         OtherParserBlock();
481                         Initialize(Span);
482                     }
483                 }
484                 else
485                 {
486                     Accept(transition);
487                     Accept(transition2);
488                 }
489             }
490             else
491             {
492                 Accept(lastWhiteSpace);
493 
494                 PutCurrentBack();
495                 PutBack(transition);
496 
497                 bool old = IsNested;
498                 IsNested = true;
499                 NestedBlock();
500                 IsNested = old;
501             }
502         }
503 
OutputSpanBeforeRazorComment()504         protected override void OutputSpanBeforeRazorComment()
505         {
506             Output(SpanKind.Code);
507         }
508 
ReservedWord()509         protected bool ReservedWord()
510         {
511             Context.CurrentBlock.Type = BlockType.Directive;
512             Context.OnError(CurrentLocation, RazorResources.ParseError_ReservedWord, CurrentSymbol.Content);
513             Span.CodeGenerator = SpanCodeGenerator.Null;
514             AcceptAndMoveNext();
515             Output(SpanKind.MetaCode, AcceptedCharacters.None);
516             return true;
517         }
518 
NestedBlock()519         protected void NestedBlock()
520         {
521             using (PushSpanConfig())
522             {
523                 Output(SpanKind.Code);
524 
525                 bool old = DirectParentIsCode;
526                 DirectParentIsCode = true;
527 
528                 ParseBlock();
529 
530                 DirectParentIsCode = old;
531             }
532             Initialize(Span);
533         }
534 
Required(VBSymbolType expected, string errorBase)535         protected bool Required(VBSymbolType expected, string errorBase)
536         {
537             if (!Optional(expected))
538             {
539                 Context.OnError(CurrentLocation, errorBase, GetCurrentSymbolDisplay());
540                 return false;
541             }
542             return true;
543         }
544 
Optional(VBKeyword keyword)545         protected bool Optional(VBKeyword keyword)
546         {
547             if (At(keyword))
548             {
549                 AcceptAndMoveNext();
550                 return true;
551             }
552             return false;
553         }
554 
AcceptVBSpaces()555         protected void AcceptVBSpaces()
556         {
557             Accept(ReadVBSpacesLazy());
558         }
559 
ReadVBSpaces()560         protected IEnumerable<VBSymbol> ReadVBSpaces()
561         {
562             return ReadVBSpacesLazy().ToList();
563         }
564 
IsDirectiveDefined(string directive)565         public bool IsDirectiveDefined(string directive)
566         {
567             return _directiveHandlers.ContainsKey(directive);
568         }
569 
ReadVBSpacesLazy()570         private IEnumerable<VBSymbol> ReadVBSpacesLazy()
571         {
572             foreach (var symbol in ReadWhileLazy(sym => sym.Type == VBSymbolType.WhiteSpace))
573             {
574                 yield return symbol;
575             }
576             while (At(VBSymbolType.LineContinuation))
577             {
578                 int bookmark = CurrentLocation.AbsoluteIndex;
579                 VBSymbol under = CurrentSymbol;
580                 NextToken();
581                 if (At(VBSymbolType.NewLine))
582                 {
583                     yield return under;
584                     yield return CurrentSymbol;
585                     NextToken();
586                     foreach (var symbol in ReadVBSpaces())
587                     {
588                         yield return symbol;
589                     }
590                 }
591                 else
592                 {
593                     Context.Source.Position = bookmark;
594                     NextToken();
595                     yield break;
596                 }
597             }
598         }
599     }
600 }
601