e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
354 lines
15 KiB
C#
354 lines
15 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="CompilerScopeManager.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="true">[....]</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];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|