protected bool TryRecover(RecoveryModes mode, Predicate <char> condition, bool allowTransition, SpanFactory previousSpanFactory) { bool anyNewLines = false; while (!EndOfFile && !condition(CurrentCharacter)) { // Eat whitespace Context.AcceptWhiteSpace(false); if (mode.HasFlag(RecoveryModes.Markup)) { // If we see new lines before a '<' then assume it's markup. // This isn't 100% correct but it is the 80/90% case when typing razor code if (anyNewLines && Context.MarkupParser.IsStartTag()) { return(true); } // End tags are mostly unambiguous // REVIEW: Does this make sense? if (Context.MarkupParser.IsEndTag()) { return(true); } } if (mode.HasFlag(RecoveryModes.Code)) { if (TryRecover(allowTransition, previousSpanFactory)) { return(true); } } if (mode.HasFlag(RecoveryModes.Transition)) { if (CurrentCharacter == RazorParser.TransitionCharacter) { return(true); } } anyNewLines = ParserHelpers.IsNewLine(CurrentCharacter); Context.AcceptCurrent(); } return(false); }
private void ParseEndPsuedoTag(Stack <TagInfo> tags, TagInfo tag, bool inDocument) { Debug.Assert(tag.IsEndTag, "ParseEndPsuedoTag requires an end tag"); // Collect what we've seen so far, since the "</text>" is not a markup span, it's a transition span Span prev = null; if (HaveContent) { prev = MarkupSpan.Create(Context); Context.ResetBuffers(); } // Accept the "</text>" Context.Expect("<"); Context.AcceptWhiteSpace(includeNewLines: true); Context.Expect("/text"); bool complete = CurrentCharacter == '>'; if (!complete) { OnError(tag.Start, RazorResources.ParseError_TextTagCannotContainAttributes); } else { Context.AcceptCurrent(); } // Remove the tag UpdateTagStack(tags, tag, !inDocument); if (tags.Count == 0) { // That was the top-level tag, output the markup then a transition span for the </text> if (prev != null) { Output(prev); } End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: complete ? AcceptedCharacters.None : AcceptedCharacters.Any)); } else { // Wasn't top-level, so resume the original span Context.ResumeSpan(prev); } }
protected virtual void AcceptWhiteSpaceByLines() { Debug.Assert(InTemporaryBuffer); // Eat whitespace until a non-whitespace character, while (Char.IsWhiteSpace(CurrentCharacter)) { Context.AcceptWhiteSpace(includeNewLines: false); // Stopped because of a newline, so accept the newline, then accept the temporary buffer and start it again if (Char.IsWhiteSpace(CurrentCharacter)) { Context.AcceptLine(includeNewLineSequence: true); Context.AcceptTemporaryBuffer(); Context.StartTemporaryBuffer(); } } }
protected bool TryParseComment(SpanFactory previousSpanFactory) { // Check for comment using (Context.StartTemporaryBuffer()) { Context.AcceptWhiteSpace(includeNewLines: true); if (Context.Peek(RazorParser.StartCommentSequence, caseSensitive: true)) { Context.AcceptTemporaryBuffer(); End(previousSpanFactory); } else { return(false); } } ParseComment(); return(true); }
/// <summary> /// Parses the modeltype statement. /// </summary> /// <param name="block">The code block.</param> public bool ParseModelTypeStatement(CodeBlockInfo block) { Contract.Requires(block != null); using (StartBlock(BlockType.Directive)) { block.ResumeSpans(Context); SourceLocation location = CurrentLocation; bool readWhitespace = RequireSingleWhiteSpace(); End(MetaCodeSpan.Create(Context, false, readWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any)); if (_modelOrInheritsStatementFound) { OnError(location, "The modeltype or inherits keywords can only appear once."); } _modelOrInheritsStatementFound = true; // Accept Whitespace up to the new line or non-whitespace character Context.AcceptWhiteSpace(false); string typeName = null; if (ParserHelpers.IsIdentifierStart(CurrentCharacter)) { using (Context.StartTemporaryBuffer()) { Context.AcceptUntil(ParserHelpers.IsNewLine); typeName = Context.ContentBuffer.ToString(); Context.AcceptTemporaryBuffer(); } Context.AcceptNewLine(); } else { OnError(location, "Expected model identifier."); } End(new ModelSpan(Context, typeName)); } return(false); }
private bool ParseModelStatement(CodeBlockInfo block) { using (StartBlock(BlockType.Directive)) { block.ResumeSpans(Context); SourceLocation endModelLocation = CurrentLocation; bool readWhitespace = RequireSingleWhiteSpace(); End(MetaCodeSpan.Create(Context, hidden: false, acceptedCharacters: readWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any)); if (_modelStatementFound) { OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture, MvcResources.MvcRazorCodeParser_OnlyOneModelStatementIsAllowed, ModelTypeKeyword)); } _modelStatementFound = true; // Accept Whitespace up to the new line or non-whitespace character Context.AcceptWhiteSpace(includeNewLines: false); string typeName = null; if (ParserHelpers.IsIdentifierStart(CurrentCharacter)) { using (Context.StartTemporaryBuffer()) { Context.AcceptUntil(c => ParserHelpers.IsNewLine(c)); typeName = Context.ContentBuffer.ToString(); Context.AcceptTemporaryBuffer(); } Context.AcceptNewLine(); } else { OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture, MvcResources.MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName, ModelTypeKeyword)); } CheckForInheritsAndModelStatements(); End(new ModelSpan(Context, typeName)); } return(false); }
/// <summary> /// Parses the model statement. /// </summary> /// <param name="block">The code block.</param> private bool ParseModelStatement(CodeBlockInfo block) { var location = CurrentLocation; bool readWhiteSpace = RequireSingleWhiteSpace(); End(MetaCodeSpan.Create(Context, false, readWhiteSpace ? AcceptedCharacters.None : AcceptedCharacters.Any)); if (_modelOrInheritsStatementFound) { OnError(location, "The model or inherits keywords can only appear once."); } _modelOrInheritsStatementFound = true; Context.AcceptWhiteSpace(false); string typeName = null; if (ParserHelpers.IsIdentifierStart(CurrentCharacter)) { using (Context.StartTemporaryBuffer()) { Context.AcceptUntil(ParserHelpers.IsNewLine); typeName = Context.ContentBuffer.ToString(); Context.AcceptTemporaryBuffer(); } Context.AcceptNewLine(); } else { OnError(location, "Expected model identifier."); } End(new ModelSpan(Context, typeName)); return(false); }
private bool ParseResourceStatement(CodeBlockInfo block) { End(MetaCodeSpan.Create); SourceLocation endModelLocation = CurrentLocation; if (_modelStatementFound) { OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture, "Only one '{0}' statement is allowed in a file.", ResourceKeyword)); } _modelStatementFound = true; // Accept Whitespace up to the new line or non-whitespace character Context.AcceptWhiteSpace(false); string typeName = null; if (ParserHelpers.IsIdentifierStart(CurrentCharacter)) { using (Context.StartTemporaryBuffer()) { // Accept a dotted-identifier, but allow <> AcceptTypeName(); typeName = Context.ContentBuffer.ToString(); Context.AcceptTemporaryBuffer(); } } else { OnError(endModelLocation, String.Format(CultureInfo.CurrentCulture, "The '{0}' keyword must be followed by a type name on the same line.", ResourceKeyword)); } CheckForInheritsAndResourceStatements(); End(ResourceSpan.Create(Context, typeName)); return(false); }
private bool ParseStartPsuedoTag(Stack <TagInfo> tags, TagInfo tag) { Debug.Assert(!tag.IsEndTag, "ParseStartPsuedoTag requires a start tag"); // Output what we've seen so far, since the "<text>" is not a markup token, it's a transition token if (HaveContent) { End(MarkupSpan.Create); } // Accept the "<text>" Context.Expect("<"); Context.AcceptWhiteSpace(includeNewLines: true); Context.Expect("text"); bool isValid = false; using (Context.StartTemporaryBuffer()) { Context.AcceptWhiteSpace(includeNewLines: true); if (CurrentCharacter == '/' || CurrentCharacter == '>') { isValid = true; Context.AcceptTemporaryBuffer(); } } bool transitionComplete = false; bool isEmpty = false; if (!isValid) { OnError(tag.Start, RazorResources.ParseError_TextTagCannotContainAttributes); } else { isEmpty = CurrentCharacter == '/'; Context.AcceptCurrent(); if (isEmpty) { if (CurrentCharacter != '>') { OnError(CurrentLocation, RazorResources.ParseError_SlashInEmptyTagMustBeFollowedByCloseAngle); } else { transitionComplete = true; Context.AcceptCurrent(); } } else { transitionComplete = true; } } // Output the transition End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: transitionComplete ? AcceptedCharacters.None : AcceptedCharacters.Any)); // Push it on to the stack and continue if (!isEmpty) { tags.Push(tag); } return(isEmpty); }
private bool ParseTagBlock(bool inDocument) { // For tracking end tags Stack <TagInfo> tags = new Stack <TagInfo>(); bool startedByPseudoTag = false; bool?canGrow = null; do { // Append until the next tag, processing code as we find it AppendUntilAndParseCode(c => c == '<'); // Read the tag name in lookahead since we might not actually want to accept it TagInfo tag = ParseStartOfTag(); // Special case for "<text>" tag as the first tag we've seen if (IsPsuedoTagValidHere(inDocument, tags, startedByPseudoTag, tag)) { if (!tag.IsEndTag) { startedByPseudoTag = true; // Set a flag to indicate that a </text> is a valid end tag if (ParseStartPsuedoTag(tags, tag)) { // Can't just do "canGrow = !ParseStartPsuedoTag(...)" because we can't canGrow to // stay null if we get false from ParseStartPsuedoTag canGrow = false; } } else { ParseEndPsuedoTag(tags, tag, inDocument); canGrow = false; } } // It wasn't a "<text>" OR it was but it was within a block, so we don't do anything special else { // We're at the '<' Context.AcceptCurrent(); // "<" if (tag.IsEndTag) { Context.AcceptCurrent(); // "/" } Context.AcceptCharacters(tag.Name.Length); // tag name // Invalid tag name? Not a real tag if (!String.IsNullOrEmpty(tag.Name)) { // What kind of tag is it bool?unterminated = null; switch (tag.Name[0]) { case '!': unterminated = ParseBangTag(tag.Name); break; case '?': unterminated = ParseProcessingInstruction(); break; default: if (tag.IsEndTag) { unterminated = ParseEndTag(tags, tag, inDocument); } else { unterminated = ParseStartTag(tags, tag); } break; } if (tags.Count == 0 && unterminated != null) { canGrow = unterminated.Value; } } else { canGrow = true; if (tags.Count == 0) { OnError(CurrentLocation, RazorResources.ParseError_OuterTagMissingName); } } } } while (!EndOfFile && tags.Count > 0); if (canGrow == null) { canGrow = tags.Count > 0; } if (tags.Count > 0) { // Ended because of EOF, not matching close tag. Throw error for last tag while (tags.Count > 1) { tags.Pop(); } TagInfo tag = tags.Pop(); OnError(tag.Start, RazorResources.ParseError_MissingEndTag, tag.Name); } // Add the remaining whitespace (up to and including the next newline) to the token if run-time mode if (!DesignTimeMode) { // Dev10 Bug 884969 - Emit space between markup and code Context.AcceptWhiteSpace(includeNewLines: false); if (Char.IsWhiteSpace(CurrentCharacter)) { Context.AcceptLine(includeNewLineSequence: true); } } else if (canGrow.Value) { Context.AcceptWhiteSpace(includeNewLines: false); if (ParserHelpers.IsNewLine(CurrentCharacter)) { Context.AcceptNewLine(); } } return(canGrow.Value); }
public override void ParseBlock() { if (Context == null) { throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set); } using (StartBlock(BlockType.Markup)) { // An HTML block starts with a start tag and ends with the matching end tag. For example, each of the following lines are HTML blocks: // <li>foo</li> // <li>foo<b>bar</b></li> // <text>This block uses the <pre><text></pre> pseudo-tag which is not emitted, but is used for balancing tags</text> // Or, if it starts with a ":", then it is part of the "@:" single-line markup construct. The ":" is discarded and the rest of the line is markup // For example, each of the following lines are HTML blocks: // :this is all markup, except for the initial ':' // :you <b>can</b> put tags in here <em>and</em> they don't have to be <img src="foo.jpg"> balanced! SpanFactory spanFactory = MarkupSpan.Create; Context.AcceptWhiteSpace(includeNewLines: true); if (CurrentCharacter == RazorParser.TransitionCharacter) { if (HaveContent) { End(MarkupSpan.Create); } Context.AcceptCurrent(); Debug.Assert(HaveContent); End(TransitionSpan.Create(Context, hidden: false, acceptedCharacters: AcceptedCharacters.None)); if (CurrentCharacter == RazorParser.TransitionCharacter) { // Second "@" is for VB // TODO: Refactor!!! Context.AcceptCurrent(); End(MetaCodeSpan.Create); } } bool complete = false; if (CurrentCharacter == ':') { // Parse a single line of markup spanFactory = SingleLineMarkupSpan.Create; Context.WhiteSpaceIsImportantToAncestorBlock = true; complete = !ParseSingleLineBlock(); Context.WhiteSpaceIsImportantToAncestorBlock = false; } else if (CurrentCharacter == '<') { complete = !ParseTagBlock(false); } else { // First non-whitespace character in a block must be a start tag or ':' OnError(CurrentLocation, RazorResources.ParseError_MarkupBlock_Must_Start_With_Tag); return; } // Output anything that's left over // If we have content ==> Output // If the previous span can't grow ==> Output UNLESS we're "complete" if ((!complete && !Context.PreviousSpanCanGrow) || HaveContent) { Span span = spanFactory(Context); span.AcceptedCharacters = complete ? AcceptedCharacters.None : AcceptedCharacters.Any; End(span); } } }
protected virtual bool BalanceBrackets(bool allowTransition, SpanFactory spanFactory, bool appendOuter, char?bracket, bool useTemporaryBuffer) { spanFactory = spanFactory ?? CodeSpan.Create; if (useTemporaryBuffer) { Context.StartTemporaryBuffer(); } int nesting = 0; // Nesting level bool callerReadBracket = true; if (bracket == null) { callerReadBracket = false; bracket = CurrentCharacter; } else { // The caller already read the bracket, so start at nesting level 1, and also, don't append the outer bracket nesting = 1; } char terminator = _bracketPairs[bracket.Value]; do { // Gather whitespace Context.StartTemporaryBuffer(); AcceptWhiteSpaceByLines(); if (CurrentCharacter == RazorParser.TransitionCharacter) { if (Context.Peek(RazorParser.StartCommentSequence, caseSensitive: true)) { Context.AcceptTemporaryBuffer(); if (useTemporaryBuffer) { Context.AcceptTemporaryBuffer(); } End(spanFactory); ParseComment(); if (useTemporaryBuffer) { Context.StartTemporaryBuffer(); } } else if (allowTransition) { Context.RejectTemporaryBuffer(); if (!HandleTransition(spanFactory)) { Context.AcceptWhiteSpace(includeNewLines: true); if (!TryAcceptStringOrComment()) { Context.AssertCurrent(RazorParser.TransitionCharacter); Context.AcceptCurrent(); } } else if (useTemporaryBuffer) { // Start a new outer temporary buffer Context.StartTemporaryBuffer(); } } else { Context.AcceptTemporaryBuffer(); if (!TryAcceptStringOrComment()) { Context.AcceptCurrent(); } } } else { Context.AcceptTemporaryBuffer(); } AcceptUntilUnquoted(c => Char.IsWhiteSpace(c) || c == bracket || c == terminator || c == RazorParser.TransitionCharacter); if (CurrentCharacter == terminator) { // If the nesting level is 1 and no bracket was specified, don't read the terminator, but we are done if (nesting == 1 && callerReadBracket) { nesting--; } else { AcceptOrSkipCurrent(appendOuter, --nesting); } } else if (CurrentCharacter == bracket) { AcceptOrSkipCurrent(appendOuter, nesting++); } } while (!EndOfFile && nesting > 0); if (useTemporaryBuffer) { if (nesting > 0) { Context.RejectTemporaryBuffer(); } else { Context.AcceptTemporaryBuffer(); } } Debug.Assert(!InTemporaryBuffer); return(nesting == 0); // Return a boolean indicating if we exited because of EOF or because of the end bracket }