File: System\Xml\Xsl\Xslt\CompilerScopeManager.cs
Project: ndp\fx\src\XmlUtils\System.Data.SqlXml.csproj (System.Data.SqlXml)
//------------------------------------------------------------------------------
// <copyright file="CompilerScopeManager.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
 
using System.Diagnostics;
 
namespace System.Xml.Xsl.Xslt {
    using QilName = System.Xml.Xsl.Qil.QilName;
 
    // Compiler scope manager keeps track of
    //   Variable declarations
    //   Namespace declarations
    //   Extension and excluded namespaces
    internal sealed class CompilerScopeManager<V> {
        public enum ScopeFlags {
            BackwardCompatibility = 0x1,
            ForwardCompatibility = 0x2,
            CanHaveApplyImports = 0x4,
            NsDecl   = 0x10,                   // NS declaration
            NsExcl   = 0x20,                   // NS Extencion (null for ExcludeAll)
            Variable = 0x40,
 
            CompatibilityFlags = BackwardCompatibility | ForwardCompatibility,
            InheritedFlags = CompatibilityFlags | CanHaveApplyImports,
            ExclusiveFlags = NsDecl | NsExcl | Variable
        }
 
        public struct ScopeRecord {
            public int    scopeCount;
            public ScopeFlags flags;
            public string ncName;     // local-name for variable, prefix for namespace, null for extension or excluded namespace
            public string nsUri;      // namespace uri
            public V      value;      // value for variable, null for namespace
 
            // Exactly one of these three properties is true for every given record
            public bool IsVariable      { get { return (flags & ScopeFlags.Variable) != 0; } }
            public bool IsNamespace     { get { return (flags & ScopeFlags.NsDecl  ) != 0; } }
//          public bool IsExNamespace   { get { return (flags & ScopeFlags.NsExcl  ) != 0; } }
        }
 
        // Number of predefined records minus one
        private const int       LastPredefRecord = 0;
 
        private ScopeRecord[]   records = new ScopeRecord[32];
        private int             lastRecord = LastPredefRecord;
 
        // This is cache of records[lastRecord].scopeCount field;
        // most often we will have PushScope()/PopScope pare over the same record.
        // It has sence to avoid adresing this field through array access.
        private int             lastScopes = 0;
 
        public CompilerScopeManager() {
            // The prefix 'xml' is by definition bound to the namespace name http://www.w3.org/XML/1998/namespace
            records[0].flags  = ScopeFlags.NsDecl;
            records[0].ncName = "xml";
            records[0].nsUri  = XmlReservedNs.NsXml;
        }
 
        public CompilerScopeManager(KeywordsTable atoms) {
            records[0].flags  = ScopeFlags.NsDecl;
            records[0].ncName = atoms.Xml;
            records[0].nsUri  = atoms.UriXml;
        }
 
        public void EnterScope() {
            lastScopes++;
        }
 
        public void ExitScope() {
            if (0 < lastScopes) {
                lastScopes--;
            } else {
                while (records[--lastRecord].scopeCount == 0) {
                }
                lastScopes = records[lastRecord].scopeCount;
                lastScopes--;
            }
        }
 
        [Conditional("DEBUG")]
        public void CheckEmpty() {
            ExitScope();
            Debug.Assert(lastRecord == 0 && lastScopes == 0, "PushScope() and PopScope() calls are unbalanced");
        }
 
        // returns true if ns decls was added to scope
        public bool EnterScope(NsDecl nsDecl) {
            lastScopes++;
 
            bool hasNamespaces = false;
            bool excludeAll    = false;
            for (; nsDecl != null;  nsDecl = nsDecl.Prev) {
                if (nsDecl.NsUri == null) {
                    Debug.Assert(nsDecl.Prefix == null, "NS may be null only when prefix is null where it is used for extension-element-prefixes='#all'");
                    excludeAll = true;
                } else if (nsDecl.Prefix == null) {
                    AddExNamespace(nsDecl.NsUri);
                } else {
                    hasNamespaces = true;
                    AddNsDeclaration(nsDecl.Prefix, nsDecl.NsUri);
                }
            }
            if (excludeAll) {
                // #all should be on the top of the stack, becase all NSs on this element should be excluded as well
                AddExNamespace(null);
            }
            return hasNamespaces;
        }
 
        private void AddRecord() {
            // Store cached fields:
            records[lastRecord].scopeCount = lastScopes;
            // Extend record buffer:
            if (++lastRecord == records.Length) {
                ScopeRecord[] newRecords = new ScopeRecord[lastRecord * 2];
                Array.Copy(records, 0, newRecords, 0, lastRecord);
                records = newRecords;
            }
            // reset scope count:
            lastScopes = 0;
        }
 
        private void AddRecord(ScopeFlags flag, string ncName, string uri, V value) {
            Debug.Assert(flag == (flag & ScopeFlags.ExclusiveFlags) && (flag & (flag - 1)) == 0 && flag != 0, "One exclusive flag");
            Debug.Assert(uri != null || ncName == null, "null, null means exclude '#all'");
 
            ScopeFlags flags = records[lastRecord].flags;
            bool canReuseLastRecord = (lastScopes == 0) && (flags & ScopeFlags.ExclusiveFlags) == 0;
            if (!canReuseLastRecord) {
                AddRecord();
                flags &= ScopeFlags.InheritedFlags;
            }
 
            records[lastRecord].flags   = flags | flag;
            records[lastRecord].ncName  = ncName;
            records[lastRecord].nsUri   = uri;
            records[lastRecord].value   = value;
        }
 
        private void SetFlag(ScopeFlags flag, bool value) {
            Debug.Assert(flag == (flag & ScopeFlags.InheritedFlags) && (flag & (flag - 1)) == 0 && flag != 0, "one inherited flag");
            ScopeFlags flags = records[lastRecord].flags;
            if (((flags & flag) != 0) != value) {
                // lastScopes == records[lastRecord].scopeCount;          // we know this because we are cashing it.
                bool canReuseLastRecord = lastScopes == 0;                // last record is from last scope
                if (!canReuseLastRecord) {
                    AddRecord();
                    flags &= ScopeFlags.InheritedFlags;
                }
                if (flag == ScopeFlags.CanHaveApplyImports) {
                    flags ^= flag;
                } else {
                    flags &= ~ScopeFlags.CompatibilityFlags;
                    if (value) {
                        flags |= flag;
                    }
                }
                records[lastRecord].flags = flags;
            }
            Debug.Assert((records[lastRecord].flags & ScopeFlags.CompatibilityFlags) != ScopeFlags.CompatibilityFlags,
                "BackwardCompatibility and ForwardCompatibility flags are mutually exclusive"
            );
        }
 
        // Add variable to the current scope.  Returns false in case of duplicates.
        public void AddVariable(QilName varName, V value) {
            Debug.Assert(varName.LocalName != null && varName.NamespaceUri != null);
            AddRecord(ScopeFlags.Variable, varName.LocalName, varName.NamespaceUri, value);
        }
 
        // Since the prefix might be redefined in an inner scope, we search in descending order in [to, from]
        // If interval is empty (from < to), the function returns null.
        private string LookupNamespace(string prefix, int from, int to) {
            Debug.Assert(prefix != null);
            for (int record = from; to <= record; --record) {
                string recPrefix, recNsUri;
                ScopeFlags flags = GetName(ref records[record], out recPrefix, out recNsUri);
                if (
                    (flags & ScopeFlags.NsDecl) != 0 &&
                    recPrefix == prefix
                ) {
                    return recNsUri;
                }
            }
            return null;
        }
 
        public string LookupNamespace(string prefix) {
            return LookupNamespace(prefix, lastRecord, 0);
        }
 
        private static ScopeFlags GetName(ref ScopeRecord re, out string prefix, out string nsUri) {
            prefix = re.ncName;
            nsUri  = re.nsUri;
            return re.flags;
        }
 
        public void AddNsDeclaration(string prefix, string nsUri) {
            AddRecord(ScopeFlags.NsDecl, prefix, nsUri, default(V));
        }
 
        public void AddExNamespace(string nsUri) {
            AddRecord(ScopeFlags.NsExcl, null, nsUri, default(V));
        }
 
        public bool IsExNamespace(string nsUri) {
            Debug.Assert(nsUri != null);
            int exAll = 0;
            for (int record = lastRecord; 0 <= record; record--) {
                string recPrefix, recNsUri;
                ScopeFlags flags = GetName(ref records[record], out recPrefix, out recNsUri);
                if ((flags & ScopeFlags.NsExcl) != 0) {
                    Debug.Assert(recPrefix == null);
                    if (recNsUri == nsUri) {
                        return true;               // This namespace is excluded
                    }
                    if (recNsUri == null) {
                        exAll = record;            // #all namespaces below are excluded
                    }
                } else if (
                    exAll != 0 &&
                    (flags & ScopeFlags.NsDecl) != 0 &&
                    recNsUri == nsUri
                ) {
                    // We need to check that this namespace wasn't undefined before last "#all"
                    bool undefined = false;
                    for (int prev = record + 1; prev < exAll; prev++) {
                        string prevPrefix, prevNsUri;
                        ScopeFlags prevFlags = GetName(ref records[prev], out prevPrefix, out prevNsUri);
                        if (
                            (flags & ScopeFlags.NsDecl) != 0 &&
                            prevPrefix == recPrefix
                        ) {
                            // We don't care if records[prev].nsUri == records[record].nsUri.
                            // In this case the namespace was already undefined above.
                            undefined = true;
                            break;
                        }
                    }
                    if (!undefined) {
                        return true;
                    }
                }
            }
            return false;
        }
 
        private int SearchVariable(string localName, string uri) {
            Debug.Assert(localName != null);
            for (int record = lastRecord; 0 <= record; --record) {
                string recLocal, recNsUri;
                ScopeFlags flags = GetName(ref records[record], out recLocal, out recNsUri);
                if (
                    (flags & ScopeFlags.Variable) != 0 &&
                    recLocal == localName &&
                    recNsUri == uri
                ) {
                    return record;
                }
            }
            return -1;
        }
 
        public V LookupVariable(string localName, string uri) {
            int record = SearchVariable(localName, uri);
            return (record < 0) ? default(V) : records[record].value;
        }
 
        public bool IsLocalVariable(string localName, string uri) {
            int record = SearchVariable(localName, uri);
            while (0 <= --record) {
                if (records[record].scopeCount != 0) {
                    return true;
                }
            }
            return false;
        }
 
        public bool ForwardCompatibility {
            get { return (records[lastRecord].flags & ScopeFlags.ForwardCompatibility) != 0; }
            set { SetFlag(ScopeFlags.ForwardCompatibility, value); }
        }
 
        public bool BackwardCompatibility {
            get { return (records[lastRecord].flags & ScopeFlags.BackwardCompatibility) != 0; }
            set { SetFlag(ScopeFlags.BackwardCompatibility, value); }
        }
 
        public bool CanHaveApplyImports {
            get { return (records[lastRecord].flags & ScopeFlags.CanHaveApplyImports) != 0; }
            set { SetFlag(ScopeFlags.CanHaveApplyImports, value); }
        }
 
        internal System.Collections.Generic.IEnumerable<ScopeRecord> GetActiveRecords() {
            int currentRecord = this.lastRecord + 1;
            // This logic comes from NamespaceEnumerator.MoveNext but also returns variables
            while (LastPredefRecord < --currentRecord) {
                if (records[currentRecord].IsNamespace) {
                    // This is a namespace declaration
                    if (LookupNamespace(records[currentRecord].ncName, lastRecord, currentRecord + 1) != null) {
                        continue;
                    }
                    // Its prefix has not been redefined later in [currentRecord + 1, lastRecord]
                }
                yield return records[currentRecord];
            }
        }
 
        public NamespaceEnumerator GetEnumerator() {
            return new NamespaceEnumerator(this);
        }
 
        internal struct NamespaceEnumerator {
            CompilerScopeManager<V> scope;
            int                     lastRecord;
            int                     currentRecord;
 
            public NamespaceEnumerator(CompilerScopeManager<V> scope) {
                this.scope      = scope;
                this.lastRecord = scope.lastRecord;
                this.currentRecord = lastRecord + 1;
            }
 
            public void Reset() {
                currentRecord = lastRecord + 1;
            }
 
            public bool MoveNext() {
                while (LastPredefRecord < --currentRecord) {
                    if (scope.records[currentRecord].IsNamespace) {
                        // This is a namespace declaration
                        if (scope.LookupNamespace(scope.records[currentRecord].ncName, lastRecord, currentRecord + 1) == null) {
                            // Its prefix has not been redefined later in [currentRecord + 1, lastRecord]
                            return true;
                        }
                    }
                }
                return false;
            }
 
            public ScopeRecord Current {
                get {
                    Debug.Assert(LastPredefRecord <= currentRecord && currentRecord <= scope.lastRecord, "MoveNext() either was not called or returned false");
                    Debug.Assert(scope.records[currentRecord].IsNamespace);
                    return scope.records[currentRecord];
                }
            }
        }
    }
}