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.Globalization; 7 using System.Linq; 8 using System.Threading.Tasks; 9 using System.Web.Razor.Parser.SyntaxTree; 10 using System.Web.Razor.Resources; 11 using System.Web.Razor.Text; 12 using System.Web.Razor.Utils; 13 14 namespace System.Web.Razor.Parser 15 { 16 public partial class ParserContext 17 { 18 private int? _ownerTaskId; 19 20 private bool _terminated = false; 21 22 private Stack<BlockBuilder> _blockStack = new Stack<BlockBuilder>(); 23 ParserContext(ITextDocument source, ParserBase codeParser, ParserBase markupParser, ParserBase activeParser)24 public ParserContext(ITextDocument source, ParserBase codeParser, ParserBase markupParser, ParserBase activeParser) 25 { 26 if (source == null) 27 { 28 throw new ArgumentNullException("source"); 29 } 30 if (codeParser == null) 31 { 32 throw new ArgumentNullException("codeParser"); 33 } 34 if (markupParser == null) 35 { 36 throw new ArgumentNullException("markupParser"); 37 } 38 if (activeParser == null) 39 { 40 throw new ArgumentNullException("activeParser"); 41 } 42 if (activeParser != codeParser && activeParser != markupParser) 43 { 44 throw new ArgumentException(RazorResources.ActiveParser_Must_Be_Code_Or_Markup_Parser, "activeParser"); 45 } 46 47 CaptureOwnerTask(); 48 49 Source = new TextDocumentReader(source); 50 CodeParser = codeParser; 51 MarkupParser = markupParser; 52 ActiveParser = activeParser; 53 Errors = new List<RazorError>(); 54 } 55 56 public IList<RazorError> Errors { get; private set; } 57 public TextDocumentReader Source { get; set; } 58 public ParserBase CodeParser { get; private set; } 59 public ParserBase MarkupParser { get; private set; } 60 public ParserBase ActiveParser { get; private set; } 61 public bool DesignTimeMode { get; set; } 62 63 public BlockBuilder CurrentBlock 64 { 65 get { return _blockStack.Peek(); } 66 } 67 68 public Span LastSpan { get; private set; } 69 public bool WhiteSpaceIsSignificantToAncestorBlock { get; set; } 70 71 public AcceptedCharacters LastAcceptedCharacters 72 { 73 get 74 { 75 if (LastSpan == null) 76 { 77 return AcceptedCharacters.None; 78 } 79 return LastSpan.EditHandler.AcceptedCharacters; 80 } 81 } 82 83 internal Stack<BlockBuilder> BlockStack 84 { 85 get { return _blockStack; } 86 } 87 88 public char CurrentCharacter 89 { 90 get 91 { 92 if (_terminated) 93 { 94 return '\0'; 95 } 96 #if DEBUG 97 if (CheckInfiniteLoop()) 98 { 99 return '\0'; 100 } 101 #endif 102 int ch = Source.Peek(); 103 if (ch == -1) 104 { 105 return '\0'; 106 } 107 return (char)ch; 108 } 109 } 110 111 public bool EndOfFile 112 { 113 get { return _terminated || Source.Peek() == -1; } 114 } 115 AddSpan(Span span)116 public void AddSpan(Span span) 117 { 118 EnusreNotTerminated(); 119 if (_blockStack.Count == 0) 120 { 121 throw new InvalidOperationException(RazorResources.ParserContext_NoCurrentBlock); 122 } 123 _blockStack.Peek().Children.Add(span); 124 125 span.Previous = LastSpan; 126 if (LastSpan != null) 127 { 128 LastSpan.Next = span; 129 } 130 131 LastSpan = span; 132 } 133 134 /// <summary> 135 /// Starts a block of the specified type 136 /// </summary> 137 /// <param name="blockType">The type of the block to start</param> StartBlock(BlockType blockType)138 public IDisposable StartBlock(BlockType blockType) 139 { 140 EnusreNotTerminated(); 141 AssertOnOwnerTask(); 142 _blockStack.Push(new BlockBuilder() { Type = blockType }); 143 return new DisposableAction(EndBlock); 144 } 145 146 /// <summary> 147 /// Starts a block 148 /// </summary> StartBlock()149 public IDisposable StartBlock() 150 { 151 EnusreNotTerminated(); 152 AssertOnOwnerTask(); 153 _blockStack.Push(new BlockBuilder()); 154 return new DisposableAction(EndBlock); 155 } 156 157 /// <summary> 158 /// Ends the current block 159 /// </summary> EndBlock()160 public void EndBlock() 161 { 162 EnusreNotTerminated(); 163 AssertOnOwnerTask(); 164 165 if (_blockStack.Count == 0) 166 { 167 throw new InvalidOperationException(RazorResources.EndBlock_Called_Without_Matching_StartBlock); 168 } 169 if (_blockStack.Count > 1) 170 { 171 BlockBuilder block = _blockStack.Pop(); 172 _blockStack.Peek().Children.Add(block.Build()); 173 } 174 else 175 { 176 // If we're at 1, terminate the parser 177 _terminated = true; 178 } 179 } 180 181 /// <summary> 182 /// Gets a boolean indicating if any of the ancestors of the current block is of the specified type 183 /// </summary> IsWithin(BlockType type)184 public bool IsWithin(BlockType type) 185 { 186 return _blockStack.Any(b => b.Type == type); 187 } 188 SwitchActiveParser()189 public void SwitchActiveParser() 190 { 191 EnusreNotTerminated(); 192 AssertOnOwnerTask(); 193 if (ReferenceEquals(ActiveParser, CodeParser)) 194 { 195 ActiveParser = MarkupParser; 196 } 197 else 198 { 199 ActiveParser = CodeParser; 200 } 201 } 202 OnError(SourceLocation location, string message)203 public void OnError(SourceLocation location, string message) 204 { 205 EnusreNotTerminated(); 206 AssertOnOwnerTask(); 207 Errors.Add(new RazorError(message, location)); 208 } 209 OnError(SourceLocation location, string message, params object[] args)210 public void OnError(SourceLocation location, string message, params object[] args) 211 { 212 EnusreNotTerminated(); 213 AssertOnOwnerTask(); 214 OnError(location, String.Format(CultureInfo.CurrentCulture, message, args)); 215 } 216 CompleteParse()217 public ParserResults CompleteParse() 218 { 219 if (_blockStack.Count == 0) 220 { 221 throw new InvalidOperationException(RazorResources.ParserContext_CannotCompleteTree_NoRootBlock); 222 } 223 if (_blockStack.Count != 1) 224 { 225 throw new InvalidOperationException(RazorResources.ParserContext_CannotCompleteTree_OutstandingBlocks); 226 } 227 return new ParserResults(_blockStack.Pop().Build(), Errors); 228 } 229 230 [Conditional("DEBUG")] CaptureOwnerTask()231 internal void CaptureOwnerTask() 232 { 233 if (Task.CurrentId != null) 234 { 235 _ownerTaskId = Task.CurrentId; 236 } 237 } 238 239 [Conditional("DEBUG")] AssertOnOwnerTask()240 internal void AssertOnOwnerTask() 241 { 242 if (_ownerTaskId != null) 243 { 244 Debug.Assert(_ownerTaskId == Task.CurrentId); 245 } 246 } 247 248 [Conditional("DEBUG")] 249 [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "The method body is empty in Release builds")] AssertCurrent(char expected)250 internal void AssertCurrent(char expected) 251 { 252 Debug.Assert(CurrentCharacter == expected); 253 } 254 EnusreNotTerminated()255 private void EnusreNotTerminated() 256 { 257 if (_terminated) 258 { 259 throw new InvalidOperationException(RazorResources.ParserContext_ParseComplete); 260 } 261 } 262 } 263 264 // Debug Helpers 265 266 #if DEBUG 267 [DebuggerDisplay("{Unparsed}")] 268 public partial class ParserContext 269 { 270 private const int InfiniteLoopCountThreshold = 1000; 271 private int _infiniteLoopGuardCount = 0; 272 private SourceLocation? _infiniteLoopGuardLocation = null; 273 274 internal string Unparsed 275 { 276 get 277 { 278 string remaining = Source.ReadToEnd(); 279 Source.Position -= remaining.Length; 280 return remaining; 281 } 282 } 283 CheckInfiniteLoop()284 private bool CheckInfiniteLoop() 285 { 286 // Infinite loop guard 287 // Basically, if this property is accessed 1000 times in a row without having advanced the source reader to the next position, we 288 // cause a parser error 289 if (_infiniteLoopGuardLocation != null) 290 { 291 if (Source.Location == _infiniteLoopGuardLocation.Value) 292 { 293 _infiniteLoopGuardCount++; 294 if (_infiniteLoopGuardCount > InfiniteLoopCountThreshold) 295 { 296 Debug.Fail("An internal parser error is causing an infinite loop at this location."); 297 _terminated = true; 298 return true; 299 } 300 } 301 else 302 { 303 _infiniteLoopGuardCount = 0; 304 } 305 } 306 _infiniteLoopGuardLocation = Source.Location; 307 return false; 308 } 309 } 310 #endif 311 } 312