1 //------------------------------------------------------------------------------
2 // <copyright file="CompilerScopeManager.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 //------------------------------------------------------------------------------
8 using System.Diagnostics;
10 namespace System.Xml.Xsl.Xslt {
11 using QilName = System.Xml.Xsl.Qil.QilName;
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)
26 CompatibilityFlags = BackwardCompatibility | ForwardCompatibility,
27 InheritedFlags = CompatibilityFlags | CanHaveApplyImports,
28 ExclusiveFlags = NsDecl | NsExcl | Variable
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
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; } }
44 // Number of predefined records minus one
45 private const int LastPredefRecord = 0;
47 private ScopeRecord[] records = new ScopeRecord[32];
48 private int lastRecord = LastPredefRecord;
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;
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;
62 public CompilerScopeManager(KeywordsTable atoms) {
63 records[0].flags = ScopeFlags.NsDecl;
64 records[0].ncName = atoms.Xml;
65 records[0].nsUri = atoms.UriXml;
68 public void EnterScope() {
72 public void ExitScope() {
76 while (records[--lastRecord].scopeCount == 0) {
78 lastScopes = records[lastRecord].scopeCount;
83 [Conditional("DEBUG")]
84 public void CheckEmpty() {
86 Debug.Assert(lastRecord == 0 && lastScopes == 0, "PushScope() and PopScope() calls are unbalanced");
89 // returns true if ns decls was added to scope
90 public bool EnterScope(NsDecl nsDecl) {
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'");
99 } else if (nsDecl.Prefix == null) {
100 AddExNamespace(nsDecl.NsUri);
102 hasNamespaces = true;
103 AddNsDeclaration(nsDecl.Prefix, nsDecl.NsUri);
107 // #all should be on the top of the stack, becase all NSs on this element should be excluded as well
108 AddExNamespace(null);
110 return hasNamespaces;
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;
122 // reset scope count:
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'");
130 ScopeFlags flags = records[lastRecord].flags;
131 bool canReuseLastRecord = (lastScopes == 0) && (flags & ScopeFlags.ExclusiveFlags) == 0;
132 if (!canReuseLastRecord) {
134 flags &= ScopeFlags.InheritedFlags;
137 records[lastRecord].flags = flags | flag;
138 records[lastRecord].ncName = ncName;
139 records[lastRecord].nsUri = uri;
140 records[lastRecord].value = value;
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) {
151 flags &= ScopeFlags.InheritedFlags;
153 if (flag == ScopeFlags.CanHaveApplyImports) {
156 flags &= ~ScopeFlags.CompatibilityFlags;
161 records[lastRecord].flags = flags;
163 Debug.Assert((records[lastRecord].flags & ScopeFlags.CompatibilityFlags) != ScopeFlags.CompatibilityFlags,
164 "BackwardCompatibility and ForwardCompatibility flags are mutually exclusive"
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);
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);
182 (flags & ScopeFlags.NsDecl) != 0 &&
191 public string LookupNamespace(string prefix) {
192 return LookupNamespace(prefix, lastRecord, 0);
195 private static ScopeFlags GetName(ref ScopeRecord re, out string prefix, out string nsUri) {
201 public void AddNsDeclaration(string prefix, string nsUri) {
202 AddRecord(ScopeFlags.NsDecl, prefix, nsUri, default(V));
205 public void AddExNamespace(string nsUri) {
206 AddRecord(ScopeFlags.NsExcl, null, nsUri, default(V));
209 public bool IsExNamespace(string nsUri) {
210 Debug.Assert(nsUri != null);
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
220 if (recNsUri == null) {
221 exAll = record; // #all namespaces below are excluded
225 (flags & ScopeFlags.NsDecl) != 0 &&
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);
234 (flags & ScopeFlags.NsDecl) != 0 &&
235 prevPrefix == recPrefix
237 // We don't care if records[prev].nsUri == records[record].nsUri.
238 // In this case the namespace was already undefined above.
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);
257 (flags & ScopeFlags.Variable) != 0 &&
258 recLocal == localName &&
267 public V LookupVariable(string localName, string uri) {
268 int record = SearchVariable(localName, uri);
269 return (record < 0) ? default(V) : records[record].value;
272 public bool IsLocalVariable(string localName, string uri) {
273 int record = SearchVariable(localName, uri);
274 while (0 <= --record) {
275 if (records[record].scopeCount != 0) {
282 public bool ForwardCompatibility {
283 get { return (records[lastRecord].flags & ScopeFlags.ForwardCompatibility) != 0; }
284 set { SetFlag(ScopeFlags.ForwardCompatibility, value); }
287 public bool BackwardCompatibility {
288 get { return (records[lastRecord].flags & ScopeFlags.BackwardCompatibility) != 0; }
289 set { SetFlag(ScopeFlags.BackwardCompatibility, value); }
292 public bool CanHaveApplyImports {
293 get { return (records[lastRecord].flags & ScopeFlags.CanHaveApplyImports) != 0; }
294 set { SetFlag(ScopeFlags.CanHaveApplyImports, value); }
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) {
306 // Its prefix has not been redefined later in [currentRecord + 1, lastRecord]
308 yield return records[currentRecord];
312 public NamespaceEnumerator GetEnumerator() {
313 return new NamespaceEnumerator(this);
316 internal struct NamespaceEnumerator {
317 CompilerScopeManager<V> scope;
321 public NamespaceEnumerator(CompilerScopeManager<V> scope) {
323 this.lastRecord = scope.lastRecord;
324 this.currentRecord = lastRecord + 1;
327 public void Reset() {
328 currentRecord = lastRecord + 1;
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]
344 public ScopeRecord Current {
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];