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