aspnetcore/src/Microsoft.AspNet.Razor/Parser/ParserContext.cs

325 lines
9.8 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.TagHelpers;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Utils;
using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class ParserContext
{
private int? _ownerTaskId;
private bool _terminated = false;
private Stack<BlockBuilder> _blockStack = new Stack<BlockBuilder>();
private readonly ErrorSink _errorSink;
public ParserContext([NotNull] ITextDocument source,
[NotNull] ParserBase codeParser,
[NotNull] ParserBase markupParser,
[NotNull] ParserBase activeParser,
[NotNull] ErrorSink errorSink)
{
if (activeParser != codeParser && activeParser != markupParser)
{
throw new ArgumentException(RazorResources.ActiveParser_Must_Be_Code_Or_Markup_Parser, nameof(activeParser));
}
CaptureOwnerTask();
Source = new TextDocumentReader(source);
CodeParser = codeParser;
MarkupParser = markupParser;
ActiveParser = activeParser;
_errorSink = errorSink;
}
public IEnumerable<RazorError> Errors
{
get
{
return _errorSink.Errors;
}
}
public TextDocumentReader Source { get; set; }
public ParserBase CodeParser { get; private set; }
public ParserBase MarkupParser { get; private set; }
public ParserBase ActiveParser { get; private set; }
public bool DesignTimeMode { get; set; }
public BlockBuilder CurrentBlock
{
get { return _blockStack.Peek(); }
}
public Span LastSpan { get; private set; }
public bool WhiteSpaceIsSignificantToAncestorBlock { get; set; }
public AcceptedCharacters LastAcceptedCharacters
{
get
{
if (LastSpan == null)
{
return AcceptedCharacters.None;
}
return LastSpan.EditHandler.AcceptedCharacters;
}
}
internal Stack<BlockBuilder> BlockStack
{
get { return _blockStack; }
}
public char CurrentCharacter
{
get
{
if (_terminated)
{
return '\0';
}
#if DEBUG
if (CheckInfiniteLoop())
{
return '\0';
}
#endif
var ch = Source.Peek();
if (ch == -1)
{
return '\0';
}
return (char)ch;
}
}
public bool EndOfFile
{
get { return _terminated || Source.Peek() == -1; }
}
public void AddSpan(Span span)
{
EnusreNotTerminated();
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(RazorResources.ParserContext_NoCurrentBlock);
}
_blockStack.Peek().Children.Add(span);
LastSpan = span;
}
/// <summary>
/// Starts a block of the specified type
/// </summary>
/// <param name="blockType">The type of the block to start</param>
public IDisposable StartBlock(BlockType blockType)
{
EnusreNotTerminated();
AssertOnOwnerTask();
_blockStack.Push(new BlockBuilder() { Type = blockType });
return new DisposableAction(EndBlock);
}
/// <summary>
/// Starts a block
/// </summary>
public IDisposable StartBlock()
{
EnusreNotTerminated();
AssertOnOwnerTask();
_blockStack.Push(new BlockBuilder());
return new DisposableAction(EndBlock);
}
/// <summary>
/// Ends the current block
/// </summary>
public void EndBlock()
{
EnusreNotTerminated();
AssertOnOwnerTask();
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(RazorResources.EndBlock_Called_Without_Matching_StartBlock);
}
if (_blockStack.Count > 1)
{
var block = _blockStack.Pop();
_blockStack.Peek().Children.Add(block.Build());
}
else
{
// If we're at 1, terminate the parser
_terminated = true;
}
}
/// <summary>
/// Gets a boolean indicating if any of the ancestors of the current block is of the specified type
/// </summary>
public bool IsWithin(BlockType type)
{
return _blockStack.Any(b => b.Type == type);
}
public void SwitchActiveParser()
{
EnusreNotTerminated();
AssertOnOwnerTask();
if (ReferenceEquals(ActiveParser, CodeParser))
{
ActiveParser = MarkupParser;
}
else
{
ActiveParser = CodeParser;
}
}
public void OnError(RazorError error)
{
EnusreNotTerminated();
AssertOnOwnerTask();
_errorSink.OnError(error);
}
public void OnError(SourceLocation location, string message)
{
EnusreNotTerminated();
AssertOnOwnerTask();
_errorSink.OnError(location, message);
}
public void OnError(SourceLocation location, string message, params object[] args)
{
EnusreNotTerminated();
AssertOnOwnerTask();
OnError(location, string.Format(CultureInfo.CurrentCulture, message, args));
}
public ParserResults CompleteParse()
{
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(RazorResources.ParserContext_CannotCompleteTree_NoRootBlock);
}
if (_blockStack.Count != 1)
{
throw new InvalidOperationException(RazorResources.ParserContext_CannotCompleteTree_OutstandingBlocks);
}
return new ParserResults(_blockStack.Pop().Build(),
// TagHelperDescriptors are not found by default. The RazorParser is responsible
// for identifying TagHelperDescriptors and rebuilding ParserResults.
tagHelperDescriptors: Enumerable.Empty<TagHelperDescriptor>(),
errorSink: _errorSink);
}
[Conditional("DEBUG")]
internal void CaptureOwnerTask()
{
if (Task.CurrentId != null)
{
_ownerTaskId = Task.CurrentId;
}
}
[Conditional("DEBUG")]
internal void AssertOnOwnerTask()
{
if (_ownerTaskId != null)
{
Debug.Assert(_ownerTaskId == Task.CurrentId);
}
}
[Conditional("DEBUG")]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "The method body is empty in Release builds")]
internal void AssertCurrent(char expected)
{
Debug.Assert(CurrentCharacter == expected);
}
private void EnusreNotTerminated()
{
if (_terminated)
{
throw new InvalidOperationException(RazorResources.ParserContext_ParseComplete);
}
}
}
// Debug Helpers
#if DEBUG
[DebuggerDisplay("{Unparsed}")]
public partial class ParserContext
{
private const int InfiniteLoopCountThreshold = 1000;
private int _infiniteLoopGuardCount = 0;
private SourceLocation? _infiniteLoopGuardLocation = null;
internal string Unparsed
{
get
{
var remaining = Source.ReadToEnd();
Source.Position -= remaining.Length;
return remaining;
}
}
private bool CheckInfiniteLoop()
{
// Infinite loop guard
// Basically, if this property is accessed 1000 times in a row without having advanced the source reader to the next position, we
// cause a parser error
if (_infiniteLoopGuardLocation != null)
{
if (Source.Location == _infiniteLoopGuardLocation.Value)
{
_infiniteLoopGuardCount++;
if (_infiniteLoopGuardCount > InfiniteLoopCountThreshold)
{
#if NET45
// No Debug.Fail in CoreCLR
Debug.Fail("An internal parser error is causing an infinite loop at this location.");
#else
Debug.Assert(false, "An internal parser error is causing an infinite loop at this location.");
#endif
_terminated = true;
return true;
}
}
else
{
_infiniteLoopGuardCount = 0;
}
}
_infiniteLoopGuardLocation = Source.Location;
return false;
}
}
#endif
}