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