// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Web.Razor.Editor; using System.Web.Razor.Generator; using System.Web.Razor.Parser.SyntaxTree; using System.Web.Razor.Resources; using System.Web.Razor.Text; using System.Web.Razor.Tokenizer; using System.Web.Razor.Tokenizer.Symbols; namespace System.Web.Razor.Parser { public partial class VBCodeParser : TokenizerBackedParser { private void SetUpDirectives() { MapDirective(SyntaxConstants.VB.CodeKeyword, EndTerminatedDirective(SyntaxConstants.VB.CodeKeyword, BlockType.Statement, new StatementCodeGenerator(), allowMarkup: true)); MapDirective(SyntaxConstants.VB.FunctionsKeyword, EndTerminatedDirective(SyntaxConstants.VB.FunctionsKeyword, BlockType.Functions, new TypeMemberCodeGenerator(), allowMarkup: false)); MapDirective(SyntaxConstants.VB.SectionKeyword, SectionDirective); MapDirective(SyntaxConstants.VB.HelperKeyword, HelperDirective); MapDirective(SyntaxConstants.VB.LayoutKeyword, LayoutDirective); MapDirective(SyntaxConstants.VB.SessionStateKeyword, SessionStateDirective); } protected virtual bool LayoutDirective() { AssertDirective(SyntaxConstants.VB.LayoutKeyword); AcceptAndMoveNext(); Context.CurrentBlock.Type = BlockType.Directive; // Accept spaces, but not newlines bool foundSomeWhitespace = At(VBSymbolType.WhiteSpace); AcceptWhile(VBSymbolType.WhiteSpace); Output(SpanKind.MetaCode, foundSomeWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any); // First non-whitespace character starts the Layout Page, then newline ends it AcceptUntil(VBSymbolType.NewLine); Span.CodeGenerator = new SetLayoutCodeGenerator(Span.GetContent()); Span.EditHandler.EditorHints = EditorHints.LayoutPage | EditorHints.VirtualPath; bool foundNewline = Optional(VBSymbolType.NewLine); AddMarkerSymbolIfNecessary(); Output(SpanKind.MetaCode, foundNewline ? AcceptedCharacters.None : AcceptedCharacters.Any); return true; } protected virtual bool SessionStateDirective() { AssertDirective(SyntaxConstants.VB.SessionStateKeyword); AcceptAndMoveNext(); Context.CurrentBlock.Type = BlockType.Directive; // Accept spaces, but not newlines bool foundSomeWhitespace = At(VBSymbolType.WhiteSpace); AcceptWhile(VBSymbolType.WhiteSpace); Output(SpanKind.MetaCode, foundSomeWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any); // First non-whitespace character starts the session state directive, then newline ends it AcceptUntil(VBSymbolType.NewLine); var value = String.Concat(Span.Symbols.Select(sym => sym.Content)); Span.CodeGenerator = new RazorDirectiveAttributeCodeGenerator(SyntaxConstants.VB.SessionStateKeyword, value); bool foundNewline = Optional(VBSymbolType.NewLine); AddMarkerSymbolIfNecessary(); Output(SpanKind.MetaCode, foundNewline ? AcceptedCharacters.None : AcceptedCharacters.Any); return true; } protected virtual bool HelperDirective() { if (Context.IsWithin(BlockType.Helper)) { Context.OnError(CurrentLocation, RazorResources.ParseError_Helpers_Cannot_Be_Nested); } Context.CurrentBlock.Type = BlockType.Helper; SourceLocation blockStart = CurrentLocation; AssertDirective(SyntaxConstants.VB.HelperKeyword); AcceptAndMoveNext(); VBSymbolType firstAfterKeyword = VBSymbolType.Unknown; if (CurrentSymbol != null) { firstAfterKeyword = CurrentSymbol.Type; } VBSymbol remainingWs = null; if (At(VBSymbolType.NewLine)) { // Accept a _single_ new line, we'll be aborting later. AcceptAndMoveNext(); } else { remainingWs = AcceptSingleWhiteSpaceCharacter(); } if (firstAfterKeyword == VBSymbolType.WhiteSpace || firstAfterKeyword == VBSymbolType.NewLine) { Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; } Output(SpanKind.MetaCode); if (firstAfterKeyword != VBSymbolType.WhiteSpace) { string error; if (At(VBSymbolType.NewLine)) { error = RazorResources.ErrorComponent_Newline; } else if (EndOfFile) { error = RazorResources.ErrorComponent_EndOfFile; } else { error = String.Format(CultureInfo.CurrentCulture, RazorResources.ErrorComponent_Character, CurrentSymbol.Content); } Context.OnError( CurrentLocation, RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start, error); // Bail out. PutCurrentBack(); Output(SpanKind.Code); return false; } if (remainingWs != null) { Accept(remainingWs); } bool errorReported = !Required(VBSymbolType.Identifier, RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start); AcceptWhile(VBSymbolType.WhiteSpace); SourceLocation parensStart = CurrentLocation; bool headerComplete = false; if (!Optional(VBSymbolType.LeftParenthesis)) { if (!errorReported) { errorReported = true; Context.OnError(CurrentLocation, RazorResources.ParseError_MissingCharAfterHelperName, VBSymbol.GetSample(VBSymbolType.LeftParenthesis)); } } else if (!Balance(BalancingModes.NoErrorOnFailure, VBSymbolType.LeftParenthesis, VBSymbolType.RightParenthesis, parensStart)) { Context.OnError(parensStart, RazorResources.ParseError_UnterminatedHelperParameterList); } else { Expected(VBSymbolType.RightParenthesis); headerComplete = true; } AddMarkerSymbolIfNecessary(); Context.CurrentBlock.CodeGenerator = new HelperCodeGenerator( Span.GetContent(), headerComplete); AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString); Span.EditHandler = editHandler; Output(SpanKind.Code); if (headerComplete) { bool old = IsNested; IsNested = true; using (Context.StartBlock(BlockType.Statement)) { using (PushSpanConfig(StatementBlockSpanConfiguration(new StatementCodeGenerator()))) { try { if (!EndTerminatedDirectiveBody(SyntaxConstants.VB.HelperKeyword, blockStart, allowAllTransitions: true)) { if (Context.LastAcceptedCharacters != AcceptedCharacters.Any) { AddMarkerSymbolIfNecessary(); } editHandler.AutoCompleteString = SyntaxConstants.VB.EndHelperKeyword; return false; } else { return true; } } finally { Output(SpanKind.Code); IsNested = old; } } } } else { Output(SpanKind.Code); } PutCurrentBack(); return false; } protected virtual bool SectionDirective() { SourceLocation start = CurrentLocation; AssertDirective(SyntaxConstants.VB.SectionKeyword); AcceptAndMoveNext(); if (Context.IsWithin(BlockType.Section)) { Context.OnError(CurrentLocation, RazorResources.ParseError_Sections_Cannot_Be_Nested, RazorResources.SectionExample_VB); } if (At(VBSymbolType.NewLine)) { AcceptAndMoveNext(); } else { AcceptVBSpaces(); } string sectionName = null; if (!At(VBSymbolType.Identifier)) { Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start, GetCurrentSymbolDisplay()); } else { sectionName = CurrentSymbol.Content; AcceptAndMoveNext(); } Context.CurrentBlock.Type = BlockType.Section; Context.CurrentBlock.CodeGenerator = new SectionCodeGenerator(sectionName ?? String.Empty); AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString); Span.EditHandler = editHandler; PutCurrentBack(); Output(SpanKind.MetaCode); // Parse the section OtherParserBlock(null, SyntaxConstants.VB.EndSectionKeyword); Span.CodeGenerator = SpanCodeGenerator.Null; bool complete = false; if (!At(VBKeyword.End)) { Context.OnError(start, RazorResources.ParseError_BlockNotTerminated, SyntaxConstants.VB.SectionKeyword, SyntaxConstants.VB.EndSectionKeyword); editHandler.AutoCompleteString = SyntaxConstants.VB.EndSectionKeyword; } else { AcceptAndMoveNext(); AcceptWhile(VBSymbolType.WhiteSpace); if (!At(SyntaxConstants.VB.SectionKeyword)) { Context.OnError(start, RazorResources.ParseError_BlockNotTerminated, SyntaxConstants.VB.SectionKeyword, SyntaxConstants.VB.EndSectionKeyword); } else { AcceptAndMoveNext(); Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; complete = true; } } PutCurrentBack(); Output(SpanKind.MetaCode); return complete; } protected virtual Func EndTerminatedDirective(string directive, BlockType blockType, SpanCodeGenerator codeGenerator, bool allowMarkup) { return () => { SourceLocation blockStart = CurrentLocation; Context.CurrentBlock.Type = blockType; AssertDirective(directive); AcceptAndMoveNext(); Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; Span.CodeGenerator = SpanCodeGenerator.Null; Output(SpanKind.MetaCode); using (PushSpanConfig(StatementBlockSpanConfiguration(codeGenerator))) { AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString); Span.EditHandler = editHandler; if (!EndTerminatedDirectiveBody(directive, blockStart, allowMarkup)) { editHandler.AutoCompleteString = String.Concat(SyntaxConstants.VB.EndKeyword, " ", directive); return false; } return true; } }; } protected virtual bool EndTerminatedDirectiveBody(string directive, SourceLocation blockStart, bool allowAllTransitions) { while (!EndOfFile) { VBSymbol lastWhitespace = AcceptWhiteSpaceInLines(); if (IsAtEmbeddedTransition(allowTemplatesAndComments: allowAllTransitions, allowTransitions: allowAllTransitions)) { HandleEmbeddedTransition(lastWhitespace); } else { if (At(VBKeyword.End)) { Accept(lastWhitespace); VBSymbol end = CurrentSymbol; NextToken(); IEnumerable ws = ReadVBSpaces(); if (At(directive)) { if (Context.LastAcceptedCharacters != AcceptedCharacters.Any) { AddMarkerSymbolIfNecessary(end.Start); } Output(SpanKind.Code); Accept(end); Accept(ws); AcceptAndMoveNext(); Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; Span.CodeGenerator = SpanCodeGenerator.Null; Output(SpanKind.MetaCode); return true; } else { Accept(end); Accept(ws); AcceptAndMoveNext(); } } else { Accept(lastWhitespace); AcceptAndMoveNext(); } } } // This is a language keyword, so it does not need to be localized Context.OnError(blockStart, RazorResources.ParseError_BlockNotTerminated, directive, String.Concat(SyntaxConstants.VB.EndKeyword, " ", directive)); return false; } protected bool At(string directive) { return At(VBSymbolType.Identifier) && String.Equals(CurrentSymbol.Content, directive, StringComparison.OrdinalIgnoreCase); } [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "'this' is used in DEBUG builds")] [Conditional("DEBUG")] protected void AssertDirective(string directive) { Assert(VBSymbolType.Identifier); Debug.Assert(String.Equals(directive, CurrentSymbol.Content, StringComparison.OrdinalIgnoreCase)); } private string GetCurrentSymbolDisplay() { if (EndOfFile) { return RazorResources.ErrorComponent_EndOfFile; } else if (At(VBSymbolType.NewLine)) { return RazorResources.ErrorComponent_Newline; } else if (At(VBSymbolType.WhiteSpace)) { return RazorResources.ErrorComponent_Whitespace; } else { return String.Format(CultureInfo.CurrentCulture, RazorResources.ErrorComponent_Character, CurrentSymbol.Content); } } } }