// 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.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 { internal static ISet DefaultKeywords = new HashSet(StringComparer.OrdinalIgnoreCase) { "functions", "code", "section", "do", "while", "if", "select", "for", "try", "with", "synclock", "using", "imports", "inherits", "option", "helper", "namespace", "class", "layout", "sessionstate" }; private Dictionary> _keywordHandlers = new Dictionary>(); private Dictionary> _directiveHandlers = new Dictionary>(StringComparer.OrdinalIgnoreCase); [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Necessary state is initialized before calling virtual methods")] public VBCodeParser() { DirectParentIsCode = false; Keywords = new HashSet(StringComparer.OrdinalIgnoreCase); SetUpKeywords(); SetUpDirectives(); } protected internal ISet Keywords { get; private set; } protected override LanguageCharacteristics Language { get { return VBLanguageCharacteristics.Instance; } } protected override ParserBase OtherParser { get { return Context.MarkupParser; } } private bool IsNested { get; set; } private bool DirectParentIsCode { get; set; } protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions) { return (allowTransitions && Language.IsTransition(CurrentSymbol) && !Was(VBSymbolType.Dot)) || (allowTemplatesAndComments && Language.IsCommentStart(CurrentSymbol)) || (Language.IsTransition(CurrentSymbol) && NextIs(VBSymbolType.Transition)); } protected override void HandleEmbeddedTransition() { HandleEmbeddedTransition(null); } protected void HandleEmbeddedTransition(VBSymbol lastWhiteSpace) { if (At(VBSymbolType.RazorCommentTransition)) { Accept(lastWhiteSpace); RazorComment(); } else if ((At(VBSymbolType.Transition) && !Was(VBSymbolType.Dot))) { HandleTransition(lastWhiteSpace); } } public override void ParseBlock() { if (Context == null) { throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set); } using (PushSpanConfig()) { if (Context == null) { throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set); } Initialize(Span); NextToken(); using (Context.StartBlock()) { IEnumerable syms = ReadWhile(sym => sym.Type == VBSymbolType.WhiteSpace); if (At(VBSymbolType.Transition)) { Accept(syms); Span.CodeGenerator = new StatementCodeGenerator(); Output(SpanKind.Code); } else { PutBack(syms); EnsureCurrent(); } // Allow a transition span, but don't require it if (Optional(VBSymbolType.Transition)) { Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; Span.CodeGenerator = SpanCodeGenerator.Null; Output(SpanKind.Transition); } Context.CurrentBlock.Type = BlockType.Expression; Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator(); // Determine the type of the block bool isComplete = false; Action config = null; if (!EndOfFile) { switch (CurrentSymbol.Type) { case VBSymbolType.Identifier: if (!TryDirectiveBlock(ref isComplete)) { ImplicitExpression(); } break; case VBSymbolType.LeftParenthesis: isComplete = ExplicitExpression(); break; case VBSymbolType.Keyword: Context.CurrentBlock.Type = BlockType.Statement; Context.CurrentBlock.CodeGenerator = BlockCodeGenerator.Null; isComplete = KeywordBlock(); break; case VBSymbolType.WhiteSpace: case VBSymbolType.NewLine: config = ImplictExpressionSpanConfig; Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_VB); break; default: config = ImplictExpressionSpanConfig; Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_VB, CurrentSymbol.Content); break; } } else { config = ImplictExpressionSpanConfig; Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock); } using (PushSpanConfig(config)) { if (!isComplete && Span.Symbols.Count == 0 && Context.LastAcceptedCharacters != AcceptedCharacters.Any) { AddMarkerSymbolIfNecessary(); } Output(SpanKind.Code); PutCurrentBack(); } } } } private void ImplictExpressionSpanConfig(SpanBuilder span) { span.CodeGenerator = new ExpressionCodeGenerator(); span.EditHandler = new ImplicitExpressionEditHandler( Language.TokenizeString, Keywords, acceptTrailingDot: DirectParentIsCode) { AcceptedCharacters = AcceptedCharacters.NonWhiteSpace }; } private Action StatementBlockSpanConfiguration(SpanCodeGenerator codeGenerator) { return span => { span.Kind = SpanKind.Code; span.CodeGenerator = codeGenerator; span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString); }; } // Pass "complete" flag by ref, not out because some paths may not change it. private bool TryDirectiveBlock(ref bool complete) { Assert(VBSymbolType.Identifier); Func handler; if (_directiveHandlers.TryGetValue(CurrentSymbol.Content, out handler)) { Context.CurrentBlock.CodeGenerator = BlockCodeGenerator.Null; complete = handler(); return true; } return false; } private bool KeywordBlock() { Assert(VBSymbolType.Keyword); Func handler; if (_keywordHandlers.TryGetValue(CurrentSymbol.Keyword.Value, out handler)) { Span.CodeGenerator = new StatementCodeGenerator(); Context.CurrentBlock.Type = BlockType.Statement; return handler(); } else { ImplicitExpression(); return false; } } private bool ExplicitExpression() { Context.CurrentBlock.Type = BlockType.Expression; Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator(); SourceLocation start = CurrentLocation; Expected(VBSymbolType.LeftParenthesis); Span.CodeGenerator = SpanCodeGenerator.Null; Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; Output(SpanKind.MetaCode); Span.CodeGenerator = new ExpressionCodeGenerator(); using (PushSpanConfig(span => span.CodeGenerator = new ExpressionCodeGenerator())) { if (!Balance(BalancingModes.NoErrorOnFailure | BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates, VBSymbolType.LeftParenthesis, VBSymbolType.RightParenthesis, start)) { Context.OnError(start, RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, RazorResources.BlockName_ExplicitExpression, VBSymbol.GetSample(VBSymbolType.RightParenthesis), VBSymbol.GetSample(VBSymbolType.LeftParenthesis)); AcceptUntil(VBSymbolType.NewLine); AddMarkerSymbolIfNecessary(); Output(SpanKind.Code); PutCurrentBack(); return false; } else { AddMarkerSymbolIfNecessary(); Output(SpanKind.Code); Expected(VBSymbolType.RightParenthesis); Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None; Span.CodeGenerator = SpanCodeGenerator.Null; Output(SpanKind.MetaCode); PutCurrentBack(); return true; } } } private void ImplicitExpression() { Context.CurrentBlock.Type = BlockType.Expression; Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator(); using (PushSpanConfig(ImplictExpressionSpanConfig)) { Expected(VBSymbolType.Identifier, VBSymbolType.Keyword); Span.CodeGenerator = new ExpressionCodeGenerator(); while (!EndOfFile) { switch (CurrentSymbol.Type) { case VBSymbolType.LeftParenthesis: SourceLocation start = CurrentLocation; AcceptAndMoveNext(); Action oldConfig = SpanConfig; using (PushSpanConfig()) { ConfigureSpan(span => { oldConfig(span); span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any; }); Balance(BalancingModes.AllowCommentsAndTemplates, VBSymbolType.LeftParenthesis, VBSymbolType.RightParenthesis, start); } if (Optional(VBSymbolType.RightParenthesis)) { Span.EditHandler.AcceptedCharacters = AcceptedCharacters.NonWhiteSpace; } break; case VBSymbolType.Dot: VBSymbol dot = CurrentSymbol; NextToken(); if (At(VBSymbolType.Identifier) || At(VBSymbolType.Keyword)) { Accept(dot); AcceptAndMoveNext(); } else if (At(VBSymbolType.Transition)) { VBSymbol at = CurrentSymbol; NextToken(); if (At(VBSymbolType.Identifier) || At(VBSymbolType.Keyword)) { Accept(dot); Accept(at); AcceptAndMoveNext(); } else { PutBack(at); PutBack(dot); } } else { PutCurrentBack(); if (IsNested) { Accept(dot); } else { PutBack(dot); } return; } break; default: PutCurrentBack(); return; } } } } protected void MapKeyword(VBKeyword keyword, Func action) { _keywordHandlers[keyword] = action; Keywords.Add(keyword.ToString()); } protected void MapDirective(string directive, Func action) { _directiveHandlers[directive] = action; Keywords.Add(directive); } [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This only occurs in Release builds, where this method is empty by design")] [Conditional("DEBUG")] protected void Assert(VBKeyword keyword) { Debug.Assert(CurrentSymbol.Type == VBSymbolType.Keyword && CurrentSymbol.Keyword == keyword); } protected bool At(VBKeyword keyword) { return At(VBSymbolType.Keyword) && CurrentSymbol.Keyword == keyword; } protected void OtherParserBlock() { OtherParserBlock(null, null); } protected void OtherParserBlock(string startSequence, string endSequence) { using (PushSpanConfig()) { if (Span.Symbols.Count > 0) { Output(SpanKind.Code); } Context.SwitchActiveParser(); bool old = DirectParentIsCode; DirectParentIsCode = false; Debug.Assert(ReferenceEquals(Context.ActiveParser, Context.MarkupParser)); if (!String.IsNullOrEmpty(startSequence) || !String.IsNullOrEmpty(endSequence)) { Context.MarkupParser.ParseSection(Tuple.Create(startSequence, endSequence), false); } else { Context.MarkupParser.ParseBlock(); } DirectParentIsCode = old; Context.SwitchActiveParser(); EnsureCurrent(); } Initialize(Span); } protected void HandleTransition(VBSymbol lastWhiteSpace) { if (At(VBSymbolType.RazorCommentTransition)) { Accept(lastWhiteSpace); RazorComment(); return; } // Check the next character VBSymbol transition = CurrentSymbol; NextToken(); if (At(VBSymbolType.LessThan) || At(VBSymbolType.Colon)) { // Put the transition back PutCurrentBack(); PutBack(transition); // If we're in design-time mode, accept the whitespace, otherwise put it back if (Context.DesignTimeMode) { Accept(lastWhiteSpace); } else { PutBack(lastWhiteSpace); } // Switch to markup OtherParserBlock(); } else if (At(VBSymbolType.Transition)) { if (Context.IsWithin(BlockType.Template)) { Context.OnError(transition.Start, RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested); } Accept(lastWhiteSpace); VBSymbol transition2 = CurrentSymbol; NextToken(); if (At(VBSymbolType.LessThan) || At(VBSymbolType.Colon)) { PutCurrentBack(); PutBack(transition2); PutBack(transition); Output(SpanKind.Code); // Start a template block and switch to Markup using (Context.StartBlock(BlockType.Template)) { Context.CurrentBlock.CodeGenerator = new TemplateBlockCodeGenerator(); OtherParserBlock(); Initialize(Span); } } else { Accept(transition); Accept(transition2); } } else { Accept(lastWhiteSpace); PutCurrentBack(); PutBack(transition); bool old = IsNested; IsNested = true; NestedBlock(); IsNested = old; } } protected override void OutputSpanBeforeRazorComment() { Output(SpanKind.Code); } protected bool ReservedWord() { Context.CurrentBlock.Type = BlockType.Directive; Context.OnError(CurrentLocation, RazorResources.ParseError_ReservedWord, CurrentSymbol.Content); Span.CodeGenerator = SpanCodeGenerator.Null; AcceptAndMoveNext(); Output(SpanKind.MetaCode, AcceptedCharacters.None); return true; } protected void NestedBlock() { using (PushSpanConfig()) { Output(SpanKind.Code); bool old = DirectParentIsCode; DirectParentIsCode = true; ParseBlock(); DirectParentIsCode = old; } Initialize(Span); } protected bool Required(VBSymbolType expected, string errorBase) { if (!Optional(expected)) { Context.OnError(CurrentLocation, errorBase, GetCurrentSymbolDisplay()); return false; } return true; } protected bool Optional(VBKeyword keyword) { if (At(keyword)) { AcceptAndMoveNext(); return true; } return false; } protected void AcceptVBSpaces() { Accept(ReadVBSpacesLazy()); } protected IEnumerable ReadVBSpaces() { return ReadVBSpacesLazy().ToList(); } public bool IsDirectiveDefined(string directive) { return _directiveHandlers.ContainsKey(directive); } private IEnumerable ReadVBSpacesLazy() { foreach (var symbol in ReadWhileLazy(sym => sym.Type == VBSymbolType.WhiteSpace)) { yield return symbol; } while (At(VBSymbolType.LineContinuation)) { int bookmark = CurrentLocation.AbsoluteIndex; VBSymbol under = CurrentSymbol; NextToken(); if (At(VBSymbolType.NewLine)) { yield return under; yield return CurrentSymbol; NextToken(); foreach (var symbol in ReadVBSpaces()) { yield return symbol; } } else { Context.Source.Position = bookmark; NextToken(); yield break; } } } } }