1 //------------------------------------------------------------------------------ 2 // <copyright file="CompilerScopeManager.cs" company="Microsoft"> 3 // Copyright (c) Microsoft Corporation. All rights reserved. 4 // </copyright> 5 // <owner current="true" primary="true">Microsoft</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 CompilerScopeManager()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 CompilerScopeManager(KeywordsTable atoms)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 EnterScope()68 public void EnterScope() { 69 lastScopes++; 70 } 71 ExitScope()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")] CheckEmpty()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 EnterScope(NsDecl nsDecl)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 AddRecord()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 AddRecord(ScopeFlags flag, string ncName, string uri, V value)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 SetFlag(ScopeFlags flag, bool 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) { 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. AddVariable(QilName varName, V value)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. LookupNamespace(string prefix, int from, int to)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 LookupNamespace(string prefix)191 public string LookupNamespace(string prefix) { 192 return LookupNamespace(prefix, lastRecord, 0); 193 } 194 GetName(ref ScopeRecord re, out string prefix, out string nsUri)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 AddNsDeclaration(string prefix, string nsUri)201 public void AddNsDeclaration(string prefix, string nsUri) { 202 AddRecord(ScopeFlags.NsDecl, prefix, nsUri, default(V)); 203 } 204 AddExNamespace(string nsUri)205 public void AddExNamespace(string nsUri) { 206 AddRecord(ScopeFlags.NsExcl, null, nsUri, default(V)); 207 } 208 IsExNamespace(string nsUri)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 SearchVariable(string localName, string uri)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 LookupVariable(string localName, string uri)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 IsLocalVariable(string localName, string uri)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 GetActiveRecords()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 GetEnumerator()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 NamespaceEnumeratorSystem.Xml.Xsl.Xslt.CompilerScopeManager.NamespaceEnumerator321 public NamespaceEnumerator(CompilerScopeManager<V> scope) { 322 this.scope = scope; 323 this.lastRecord = scope.lastRecord; 324 this.currentRecord = lastRecord + 1; 325 } 326 ResetSystem.Xml.Xsl.Xslt.CompilerScopeManager.NamespaceEnumerator327 public void Reset() { 328 currentRecord = lastRecord + 1; 329 } 330 MoveNextSystem.Xml.Xsl.Xslt.CompilerScopeManager.NamespaceEnumerator331 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 } 354