Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.SqlXml / System / Xml / Xsl / Xslt / CompilerScopeManager.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="CompilerScopeManager.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
7
8 using System.Diagnostics;
9
10 namespace System.Xml.Xsl.Xslt {
11     using QilName = System.Xml.Xsl.Qil.QilName;
12
13     // Compiler scope manager keeps track of
14     //   Variable declarations
15     //   Namespace declarations
16     //   Extension and excluded namespaces
17     internal sealed class CompilerScopeManager<V> {
18         public enum ScopeFlags {
19             BackwardCompatibility = 0x1,
20             ForwardCompatibility = 0x2,
21             CanHaveApplyImports = 0x4,
22             NsDecl   = 0x10,                   // NS declaration
23             NsExcl   = 0x20,                   // NS Extencion (null for ExcludeAll)
24             Variable = 0x40,
25
26             CompatibilityFlags = BackwardCompatibility | ForwardCompatibility,
27             InheritedFlags = CompatibilityFlags | CanHaveApplyImports,
28             ExclusiveFlags = NsDecl | NsExcl | Variable
29         }
30
31         public struct ScopeRecord {
32             public int    scopeCount;
33             public ScopeFlags flags;
34             public string ncName;     // local-name for variable, prefix for namespace, null for extension or excluded namespace
35             public string nsUri;      // namespace uri
36             public V      value;      // value for variable, null for namespace
37
38             // Exactly one of these three properties is true for every given record
39             public bool IsVariable      { get { return (flags & ScopeFlags.Variable) != 0; } }
40             public bool IsNamespace     { get { return (flags & ScopeFlags.NsDecl  ) != 0; } }
41 //          public bool IsExNamespace   { get { return (flags & ScopeFlags.NsExcl  ) != 0; } }
42         }
43
44         // Number of predefined records minus one
45         private const int       LastPredefRecord = 0;
46
47         private ScopeRecord[]   records = new ScopeRecord[32];
48         private int             lastRecord = LastPredefRecord;
49
50         // This is cache of records[lastRecord].scopeCount field;
51         // most often we will have PushScope()/PopScope pare over the same record.
52         // It has sence to avoid adresing this field through array access.
53         private int             lastScopes = 0;
54
55         public CompilerScopeManager() {
56             // The prefix 'xml' is by definition bound to the namespace name http://www.w3.org/XML/1998/namespace
57             records[0].flags  = ScopeFlags.NsDecl;
58             records[0].ncName = "xml";
59             records[0].nsUri  = XmlReservedNs.NsXml;
60         }
61
62         public CompilerScopeManager(KeywordsTable atoms) {
63             records[0].flags  = ScopeFlags.NsDecl;
64             records[0].ncName = atoms.Xml;
65             records[0].nsUri  = atoms.UriXml;
66         }
67
68         public void EnterScope() {
69             lastScopes++;
70         }
71
72         public void ExitScope() {
73             if (0 < lastScopes) {
74                 lastScopes--;
75             } else {
76                 while (records[--lastRecord].scopeCount == 0) {
77                 }
78                 lastScopes = records[lastRecord].scopeCount;
79                 lastScopes--;
80             }
81         }
82
83         [Conditional("DEBUG")]
84         public void CheckEmpty() {
85             ExitScope();
86             Debug.Assert(lastRecord == 0 && lastScopes == 0, "PushScope() and PopScope() calls are unbalanced");
87         }
88
89         // returns true if ns decls was added to scope
90         public bool EnterScope(NsDecl nsDecl) {
91             lastScopes++;
92
93             bool hasNamespaces = false;
94             bool excludeAll    = false;
95             for (; nsDecl != null;  nsDecl = nsDecl.Prev) {
96                 if (nsDecl.NsUri == null) {
97                     Debug.Assert(nsDecl.Prefix == null, "NS may be null only when prefix is null where it is used for extension-element-prefixes='#all'");
98                     excludeAll = true;
99                 } else if (nsDecl.Prefix == null) {
100                     AddExNamespace(nsDecl.NsUri);
101                 } else {
102                     hasNamespaces = true;
103                     AddNsDeclaration(nsDecl.Prefix, nsDecl.NsUri);
104                 }
105             }
106             if (excludeAll) {
107                 // #all should be on the top of the stack, becase all NSs on this element should be excluded as well
108                 AddExNamespace(null);
109             }
110             return hasNamespaces;
111         }
112
113         private void AddRecord() {
114             // Store cached fields:
115             records[lastRecord].scopeCount = lastScopes;
116             // Extend record buffer:
117             if (++lastRecord == records.Length) {
118                 ScopeRecord[] newRecords = new ScopeRecord[lastRecord * 2];
119                 Array.Copy(records, 0, newRecords, 0, lastRecord);
120                 records = newRecords;
121             }
122             // reset scope count:
123             lastScopes = 0;
124         }
125
126         private void AddRecord(ScopeFlags flag, string ncName, string uri, V value) {
127             Debug.Assert(flag == (flag & ScopeFlags.ExclusiveFlags) && (flag & (flag - 1)) == 0 && flag != 0, "One exclusive flag");
128             Debug.Assert(uri != null || ncName == null, "null, null means exclude '#all'");
129
130             ScopeFlags flags = records[lastRecord].flags;
131             bool canReuseLastRecord = (lastScopes == 0) && (flags & ScopeFlags.ExclusiveFlags) == 0;
132             if (!canReuseLastRecord) {
133                 AddRecord();
134                 flags &= ScopeFlags.InheritedFlags;
135             }
136
137             records[lastRecord].flags   = flags | flag;
138             records[lastRecord].ncName  = ncName;
139             records[lastRecord].nsUri   = uri;
140             records[lastRecord].value   = value;
141         }
142
143         private void SetFlag(ScopeFlags flag, bool value) {
144             Debug.Assert(flag == (flag & ScopeFlags.InheritedFlags) && (flag & (flag - 1)) == 0 && flag != 0, "one inherited flag");
145             ScopeFlags flags = records[lastRecord].flags;
146             if (((flags & flag) != 0) != value) {
147                 // lastScopes == records[lastRecord].scopeCount;          // we know this because we are cashing it.
148                 bool canReuseLastRecord = lastScopes == 0;                // last record is from last scope
149                 if (!canReuseLastRecord) {
150                     AddRecord();
151                     flags &= ScopeFlags.InheritedFlags;
152                 }
153                 if (flag == ScopeFlags.CanHaveApplyImports) {
154                     flags ^= flag;
155                 } else {
156                     flags &= ~ScopeFlags.CompatibilityFlags;
157                     if (value) {
158                         flags |= flag;
159                     }
160                 }
161                 records[lastRecord].flags = flags;
162             }
163             Debug.Assert((records[lastRecord].flags & ScopeFlags.CompatibilityFlags) != ScopeFlags.CompatibilityFlags,
164                 "BackwardCompatibility and ForwardCompatibility flags are mutually exclusive"
165             );
166         }
167
168         // Add variable to the current scope.  Returns false in case of duplicates.
169         public void AddVariable(QilName varName, V value) {
170             Debug.Assert(varName.LocalName != null && varName.NamespaceUri != null);
171             AddRecord(ScopeFlags.Variable, varName.LocalName, varName.NamespaceUri, value);
172         }
173
174         // Since the prefix might be redefined in an inner scope, we search in descending order in [to, from]
175         // If interval is empty (from < to), the function returns null.
176         private string LookupNamespace(string prefix, int from, int to) {
177             Debug.Assert(prefix != null);
178             for (int record = from; to <= record; --record) {
179                 string recPrefix, recNsUri;
180                 ScopeFlags flags = GetName(ref records[record], out recPrefix, out recNsUri);
181                 if (
182                     (flags & ScopeFlags.NsDecl) != 0 &&
183                     recPrefix == prefix
184                 ) {
185                     return recNsUri;
186                 }
187             }
188             return null;
189         }
190
191         public string LookupNamespace(string prefix) {
192             return LookupNamespace(prefix, lastRecord, 0);
193         }
194
195         private static ScopeFlags GetName(ref ScopeRecord re, out string prefix, out string nsUri) {
196             prefix = re.ncName;
197             nsUri  = re.nsUri;
198             return re.flags;
199         }
200
201         public void AddNsDeclaration(string prefix, string nsUri) {
202             AddRecord(ScopeFlags.NsDecl, prefix, nsUri, default(V));
203         }
204
205         public void AddExNamespace(string nsUri) {
206             AddRecord(ScopeFlags.NsExcl, null, nsUri, default(V));
207         }
208
209         public bool IsExNamespace(string nsUri) {
210             Debug.Assert(nsUri != null);
211             int exAll = 0;
212             for (int record = lastRecord; 0 <= record; record--) {
213                 string recPrefix, recNsUri;
214                 ScopeFlags flags = GetName(ref records[record], out recPrefix, out recNsUri);
215                 if ((flags & ScopeFlags.NsExcl) != 0) {
216                     Debug.Assert(recPrefix == null);
217                     if (recNsUri == nsUri) {
218                         return true;               // This namespace is excluded
219                     }
220                     if (recNsUri == null) {
221                         exAll = record;            // #all namespaces below are excluded
222                     }
223                 } else if (
224                     exAll != 0 &&
225                     (flags & ScopeFlags.NsDecl) != 0 &&
226                     recNsUri == nsUri
227                 ) {
228                     // We need to check that this namespace wasn't undefined before last "#all"
229                     bool undefined = false;
230                     for (int prev = record + 1; prev < exAll; prev++) {
231                         string prevPrefix, prevNsUri;
232                         ScopeFlags prevFlags = GetName(ref records[prev], out prevPrefix, out prevNsUri);
233                         if (
234                             (flags & ScopeFlags.NsDecl) != 0 &&
235                             prevPrefix == recPrefix
236                         ) {
237                             // We don't care if records[prev].nsUri == records[record].nsUri.
238                             // In this case the namespace was already undefined above.
239                             undefined = true;
240                             break;
241                         }
242                     }
243                     if (!undefined) {
244                         return true;
245                     }
246                 }
247             }
248             return false;
249         }
250
251         private int SearchVariable(string localName, string uri) {
252             Debug.Assert(localName != null);
253             for (int record = lastRecord; 0 <= record; --record) {
254                 string recLocal, recNsUri;
255                 ScopeFlags flags = GetName(ref records[record], out recLocal, out recNsUri);
256                 if (
257                     (flags & ScopeFlags.Variable) != 0 &&
258                     recLocal == localName &&
259                     recNsUri == uri
260                 ) {
261                     return record;
262                 }
263             }
264             return -1;
265         }
266
267         public V LookupVariable(string localName, string uri) {
268             int record = SearchVariable(localName, uri);
269             return (record < 0) ? default(V) : records[record].value;
270         }
271
272         public bool IsLocalVariable(string localName, string uri) {
273             int record = SearchVariable(localName, uri);
274             while (0 <= --record) {
275                 if (records[record].scopeCount != 0) {
276                     return true;
277                 }
278             }
279             return false;
280         }
281
282         public bool ForwardCompatibility {
283             get { return (records[lastRecord].flags & ScopeFlags.ForwardCompatibility) != 0; }
284             set { SetFlag(ScopeFlags.ForwardCompatibility, value); }
285         }
286
287         public bool BackwardCompatibility {
288             get { return (records[lastRecord].flags & ScopeFlags.BackwardCompatibility) != 0; }
289             set { SetFlag(ScopeFlags.BackwardCompatibility, value); }
290         }
291
292         public bool CanHaveApplyImports {
293             get { return (records[lastRecord].flags & ScopeFlags.CanHaveApplyImports) != 0; }
294             set { SetFlag(ScopeFlags.CanHaveApplyImports, value); }
295         }
296
297         internal System.Collections.Generic.IEnumerable<ScopeRecord> GetActiveRecords() {
298             int currentRecord = this.lastRecord + 1;
299             // This logic comes from NamespaceEnumerator.MoveNext but also returns variables
300             while (LastPredefRecord < --currentRecord) {
301                 if (records[currentRecord].IsNamespace) {
302                     // This is a namespace declaration
303                     if (LookupNamespace(records[currentRecord].ncName, lastRecord, currentRecord + 1) != null) {
304                         continue;
305                     }
306                     // Its prefix has not been redefined later in [currentRecord + 1, lastRecord]
307                 }
308                 yield return records[currentRecord];
309             }
310         }
311
312         public NamespaceEnumerator GetEnumerator() {
313             return new NamespaceEnumerator(this);
314         }
315
316         internal struct NamespaceEnumerator {
317             CompilerScopeManager<V> scope;
318             int                     lastRecord;
319             int                     currentRecord;
320
321             public NamespaceEnumerator(CompilerScopeManager<V> scope) {
322                 this.scope      = scope;
323                 this.lastRecord = scope.lastRecord;
324                 this.currentRecord = lastRecord + 1;
325             }
326
327             public void Reset() {
328                 currentRecord = lastRecord + 1;
329             }
330
331             public bool MoveNext() {
332                 while (LastPredefRecord < --currentRecord) {
333                     if (scope.records[currentRecord].IsNamespace) {
334                         // This is a namespace declaration
335                         if (scope.LookupNamespace(scope.records[currentRecord].ncName, lastRecord, currentRecord + 1) == null) {
336                             // Its prefix has not been redefined later in [currentRecord + 1, lastRecord]
337                             return true;
338                         }
339                     }
340                 }
341                 return false;
342             }
343
344             public ScopeRecord Current {
345                 get {
346                     Debug.Assert(LastPredefRecord <= currentRecord && currentRecord <= scope.lastRecord, "MoveNext() either was not called or returned false");
347                     Debug.Assert(scope.records[currentRecord].IsNamespace);
348                     return scope.records[currentRecord];
349                 }
350             }
351         }
352     }
353 }