407 lines
14 KiB
C#
407 lines
14 KiB
C#
|
|
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
|
|||
|
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
|
|||
|
|
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Diagnostics;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Runtime.Serialization;
|
|||
|
|
using System.Text;
|
|||
|
|
using System.Text.RegularExpressions;
|
|||
|
|
using ICSharpCode.AvalonEdit.Utils;
|
|||
|
|
|
|||
|
|
namespace ICSharpCode.AvalonEdit.Highlighting.Xshd
|
|||
|
|
{
|
|||
|
|
[Serializable]
|
|||
|
|
sealed class XmlHighlightingDefinition : IHighlightingDefinition2
|
|||
|
|
{
|
|||
|
|
public string Name { get; private set; }
|
|||
|
|
|
|||
|
|
public XmlHighlightingDefinition(XshdSyntaxDefinition xshd, IHighlightingDefinitionReferenceResolver resolver)
|
|||
|
|
{
|
|||
|
|
this.Name = xshd.Name;
|
|||
|
|
// Create HighlightingRuleSet instances
|
|||
|
|
var rnev = new RegisterNamedElementsVisitor(this);
|
|||
|
|
xshd.AcceptElements(rnev);
|
|||
|
|
// Assign MainRuleSet so that references can be resolved
|
|||
|
|
foreach (XshdElement element in xshd.Elements) {
|
|||
|
|
XshdRuleSet xrs = element as XshdRuleSet;
|
|||
|
|
if (xrs != null && xrs.Name == null) {
|
|||
|
|
if (MainRuleSet != null)
|
|||
|
|
throw Error(element, "Duplicate main RuleSet. There must be only one nameless RuleSet!");
|
|||
|
|
else
|
|||
|
|
MainRuleSet = rnev.ruleSets[xrs];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (MainRuleSet == null)
|
|||
|
|
throw new HighlightingDefinitionInvalidException("Could not find main RuleSet.");
|
|||
|
|
// Translate elements within the rulesets (resolving references and processing imports)
|
|||
|
|
xshd.AcceptElements(new TranslateElementVisitor(this, rnev.ruleSets, resolver));
|
|||
|
|
|
|||
|
|
foreach (var p in xshd.Elements.OfType<XshdProperty>())
|
|||
|
|
propDict.Add(p.Name, p.Value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#region RegisterNamedElements
|
|||
|
|
sealed class RegisterNamedElementsVisitor : IXshdVisitor
|
|||
|
|
{
|
|||
|
|
XmlHighlightingDefinition def;
|
|||
|
|
internal readonly Dictionary<XshdRuleSet, HighlightingRuleSet> ruleSets
|
|||
|
|
= new Dictionary<XshdRuleSet, HighlightingRuleSet>();
|
|||
|
|
|
|||
|
|
public RegisterNamedElementsVisitor(XmlHighlightingDefinition def)
|
|||
|
|
{
|
|||
|
|
this.def = def;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitRuleSet(XshdRuleSet ruleSet)
|
|||
|
|
{
|
|||
|
|
HighlightingRuleSet hrs = new HighlightingRuleSet();
|
|||
|
|
ruleSets.Add(ruleSet, hrs);
|
|||
|
|
if (ruleSet.Name != null) {
|
|||
|
|
if (ruleSet.Name.Length == 0)
|
|||
|
|
throw Error(ruleSet, "Name must not be the empty string");
|
|||
|
|
if (def.ruleSetDict.ContainsKey(ruleSet.Name))
|
|||
|
|
throw Error(ruleSet, "Duplicate rule set name '" + ruleSet.Name + "'.");
|
|||
|
|
|
|||
|
|
def.ruleSetDict.Add(ruleSet.Name, hrs);
|
|||
|
|
}
|
|||
|
|
ruleSet.AcceptElements(this);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitColor(XshdColor color)
|
|||
|
|
{
|
|||
|
|
if (color.Name != null) {
|
|||
|
|
if (color.Name.Length == 0)
|
|||
|
|
throw Error(color, "Name must not be the empty string");
|
|||
|
|
if (def.colorDict.ContainsKey(color.Name))
|
|||
|
|
throw Error(color, "Duplicate color name '" + color.Name + "'.");
|
|||
|
|
|
|||
|
|
def.colorDict.Add(color.Name, new HighlightingColor());
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitKeywords(XshdKeywords keywords)
|
|||
|
|
{
|
|||
|
|
return keywords.ColorReference.AcceptVisitor(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitSpan(XshdSpan span)
|
|||
|
|
{
|
|||
|
|
span.BeginColorReference.AcceptVisitor(this);
|
|||
|
|
span.SpanColorReference.AcceptVisitor(this);
|
|||
|
|
span.EndColorReference.AcceptVisitor(this);
|
|||
|
|
return span.RuleSetReference.AcceptVisitor(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitImport(XshdImport import)
|
|||
|
|
{
|
|||
|
|
return import.RuleSetReference.AcceptVisitor(this);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitRule(XshdRule rule)
|
|||
|
|
{
|
|||
|
|
return rule.ColorReference.AcceptVisitor(this);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
#region TranslateElements
|
|||
|
|
sealed class TranslateElementVisitor : IXshdVisitor
|
|||
|
|
{
|
|||
|
|
readonly XmlHighlightingDefinition def;
|
|||
|
|
readonly Dictionary<XshdRuleSet, HighlightingRuleSet> ruleSetDict;
|
|||
|
|
readonly Dictionary<HighlightingRuleSet, XshdRuleSet> reverseRuleSetDict;
|
|||
|
|
readonly IHighlightingDefinitionReferenceResolver resolver;
|
|||
|
|
HashSet<XshdRuleSet> processingStartedRuleSets = new HashSet<XshdRuleSet>();
|
|||
|
|
HashSet<XshdRuleSet> processedRuleSets = new HashSet<XshdRuleSet>();
|
|||
|
|
bool ignoreCase;
|
|||
|
|
|
|||
|
|
public TranslateElementVisitor(XmlHighlightingDefinition def, Dictionary<XshdRuleSet, HighlightingRuleSet> ruleSetDict, IHighlightingDefinitionReferenceResolver resolver)
|
|||
|
|
{
|
|||
|
|
Debug.Assert(def != null);
|
|||
|
|
Debug.Assert(ruleSetDict != null);
|
|||
|
|
this.def = def;
|
|||
|
|
this.ruleSetDict = ruleSetDict;
|
|||
|
|
this.resolver = resolver;
|
|||
|
|
reverseRuleSetDict = new Dictionary<HighlightingRuleSet, XshdRuleSet>();
|
|||
|
|
foreach (var pair in ruleSetDict) {
|
|||
|
|
reverseRuleSetDict.Add(pair.Value, pair.Key);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitRuleSet(XshdRuleSet ruleSet)
|
|||
|
|
{
|
|||
|
|
HighlightingRuleSet rs = ruleSetDict[ruleSet];
|
|||
|
|
if (processedRuleSets.Contains(ruleSet))
|
|||
|
|
return rs;
|
|||
|
|
if (!processingStartedRuleSets.Add(ruleSet))
|
|||
|
|
throw Error(ruleSet, "RuleSet cannot be processed because it contains cyclic <Import>");
|
|||
|
|
|
|||
|
|
bool oldIgnoreCase = ignoreCase;
|
|||
|
|
if (ruleSet.IgnoreCase != null)
|
|||
|
|
ignoreCase = ruleSet.IgnoreCase.Value;
|
|||
|
|
|
|||
|
|
rs.Name = ruleSet.Name;
|
|||
|
|
|
|||
|
|
foreach (XshdElement element in ruleSet.Elements) {
|
|||
|
|
object o = element.AcceptVisitor(this);
|
|||
|
|
HighlightingRuleSet elementRuleSet = o as HighlightingRuleSet;
|
|||
|
|
if (elementRuleSet != null) {
|
|||
|
|
Merge(rs, elementRuleSet);
|
|||
|
|
} else {
|
|||
|
|
HighlightingSpan span = o as HighlightingSpan;
|
|||
|
|
if (span != null) {
|
|||
|
|
rs.Spans.Add(span);
|
|||
|
|
} else {
|
|||
|
|
HighlightingRule elementRule = o as HighlightingRule;
|
|||
|
|
if (elementRule != null) {
|
|||
|
|
rs.Rules.Add(elementRule);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
ignoreCase = oldIgnoreCase;
|
|||
|
|
processedRuleSets.Add(ruleSet);
|
|||
|
|
|
|||
|
|
return rs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static void Merge(HighlightingRuleSet target, HighlightingRuleSet source)
|
|||
|
|
{
|
|||
|
|
target.Rules.AddRange(source.Rules);
|
|||
|
|
target.Spans.AddRange(source.Spans);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitColor(XshdColor color)
|
|||
|
|
{
|
|||
|
|
HighlightingColor c;
|
|||
|
|
if (color.Name != null)
|
|||
|
|
c = def.colorDict[color.Name];
|
|||
|
|
else if (color.Foreground == null && color.FontStyle == null && color.FontWeight == null)
|
|||
|
|
return null;
|
|||
|
|
else
|
|||
|
|
c = new HighlightingColor();
|
|||
|
|
|
|||
|
|
c.Name = color.Name;
|
|||
|
|
c.Foreground = color.Foreground;
|
|||
|
|
c.Background = color.Background;
|
|||
|
|
c.FontStyle = color.FontStyle;
|
|||
|
|
c.FontWeight = color.FontWeight;
|
|||
|
|
return c;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitKeywords(XshdKeywords keywords)
|
|||
|
|
{
|
|||
|
|
if (keywords.Words.Count == 0)
|
|||
|
|
return Error(keywords, "Keyword group must not be empty.");
|
|||
|
|
foreach (string keyword in keywords.Words) {
|
|||
|
|
if (string.IsNullOrEmpty(keyword))
|
|||
|
|
throw Error(keywords, "Cannot use empty string as keyword");
|
|||
|
|
}
|
|||
|
|
StringBuilder keyWordRegex = new StringBuilder();
|
|||
|
|
// We can use "\b" only where the keyword starts/ends with a letter or digit, otherwise we don't
|
|||
|
|
// highlight correctly. (example: ILAsm-Mode.xshd with ".maxstack" keyword)
|
|||
|
|
if (keywords.Words.All(IsSimpleWord)) {
|
|||
|
|
keyWordRegex.Append(@"\b(?>");
|
|||
|
|
// (?> = atomic group
|
|||
|
|
// atomic groups increase matching performance, but we
|
|||
|
|
// must ensure that the keywords are sorted correctly.
|
|||
|
|
// "\b(?>in|int)\b" does not match "int" because the atomic group captures "in".
|
|||
|
|
// To solve this, we are sorting the keywords by descending length.
|
|||
|
|
int i = 0;
|
|||
|
|
foreach (string keyword in keywords.Words.OrderByDescending(w=>w.Length)) {
|
|||
|
|
if (i++ > 0)
|
|||
|
|
keyWordRegex.Append('|');
|
|||
|
|
keyWordRegex.Append(Regex.Escape(keyword));
|
|||
|
|
}
|
|||
|
|
keyWordRegex.Append(@")\b");
|
|||
|
|
} else {
|
|||
|
|
keyWordRegex.Append('(');
|
|||
|
|
int i = 0;
|
|||
|
|
foreach (string keyword in keywords.Words) {
|
|||
|
|
if (i++ > 0)
|
|||
|
|
keyWordRegex.Append('|');
|
|||
|
|
if (char.IsLetterOrDigit(keyword[0]))
|
|||
|
|
keyWordRegex.Append(@"\b");
|
|||
|
|
keyWordRegex.Append(Regex.Escape(keyword));
|
|||
|
|
if (char.IsLetterOrDigit(keyword[keyword.Length - 1]))
|
|||
|
|
keyWordRegex.Append(@"\b");
|
|||
|
|
}
|
|||
|
|
keyWordRegex.Append(')');
|
|||
|
|
}
|
|||
|
|
return new HighlightingRule {
|
|||
|
|
Color = GetColor(keywords, keywords.ColorReference),
|
|||
|
|
Regex = CreateRegex(keywords, keyWordRegex.ToString(), XshdRegexType.Default)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static bool IsSimpleWord(string word)
|
|||
|
|
{
|
|||
|
|
return char.IsLetterOrDigit(word[0]) && char.IsLetterOrDigit(word, word.Length - 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Regex CreateRegex(XshdElement position, string regex, XshdRegexType regexType)
|
|||
|
|
{
|
|||
|
|
if (regex == null)
|
|||
|
|
throw Error(position, "Regex missing");
|
|||
|
|
RegexOptions options = RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture;
|
|||
|
|
if (regexType == XshdRegexType.IgnorePatternWhitespace)
|
|||
|
|
options |= RegexOptions.IgnorePatternWhitespace;
|
|||
|
|
if (ignoreCase)
|
|||
|
|
options |= RegexOptions.IgnoreCase;
|
|||
|
|
try {
|
|||
|
|
return new Regex(regex, options);
|
|||
|
|
} catch (ArgumentException ex) {
|
|||
|
|
throw Error(position, ex.Message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HighlightingColor GetColor(XshdElement position, XshdReference<XshdColor> colorReference)
|
|||
|
|
{
|
|||
|
|
if (colorReference.InlineElement != null) {
|
|||
|
|
return (HighlightingColor)colorReference.InlineElement.AcceptVisitor(this);
|
|||
|
|
} else if (colorReference.ReferencedElement != null) {
|
|||
|
|
IHighlightingDefinition definition = GetDefinition(position, colorReference.ReferencedDefinition);
|
|||
|
|
HighlightingColor color = definition.GetNamedColor(colorReference.ReferencedElement);
|
|||
|
|
if (color == null)
|
|||
|
|
throw Error(position, "Could not find color named '" + colorReference.ReferencedElement + "'.");
|
|||
|
|
return color;
|
|||
|
|
} else {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
IHighlightingDefinition GetDefinition(XshdElement position, string definitionName)
|
|||
|
|
{
|
|||
|
|
if (definitionName == null)
|
|||
|
|
return def;
|
|||
|
|
if (resolver == null)
|
|||
|
|
throw Error(position, "Resolving references to other syntax definitions is not possible because the IHighlightingDefinitionReferenceResolver is null.");
|
|||
|
|
IHighlightingDefinition d = resolver.GetDefinition(definitionName);
|
|||
|
|
if (d == null)
|
|||
|
|
throw Error(position, "Could not find definition with name '" + definitionName + "'.");
|
|||
|
|
return d;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
HighlightingRuleSet GetRuleSet(XshdElement position, XshdReference<XshdRuleSet> ruleSetReference)
|
|||
|
|
{
|
|||
|
|
if (ruleSetReference.InlineElement != null) {
|
|||
|
|
return (HighlightingRuleSet)ruleSetReference.InlineElement.AcceptVisitor(this);
|
|||
|
|
} else if (ruleSetReference.ReferencedElement != null) {
|
|||
|
|
IHighlightingDefinition definition = GetDefinition(position, ruleSetReference.ReferencedDefinition);
|
|||
|
|
HighlightingRuleSet ruleSet = definition.GetNamedRuleSet(ruleSetReference.ReferencedElement);
|
|||
|
|
if (ruleSet == null)
|
|||
|
|
throw Error(position, "Could not find rule set named '" + ruleSetReference.ReferencedElement + "'.");
|
|||
|
|
return ruleSet;
|
|||
|
|
} else {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitSpan(XshdSpan span)
|
|||
|
|
{
|
|||
|
|
string endRegex = span.EndRegex;
|
|||
|
|
if (string.IsNullOrEmpty(span.BeginRegex) && string.IsNullOrEmpty(span.EndRegex))
|
|||
|
|
throw Error(span, "Span has no start/end regex.");
|
|||
|
|
if (!span.Multiline) {
|
|||
|
|
if (endRegex == null)
|
|||
|
|
endRegex = "$";
|
|||
|
|
else if (span.EndRegexType == XshdRegexType.IgnorePatternWhitespace)
|
|||
|
|
endRegex = "($|" + endRegex + "\n)";
|
|||
|
|
else
|
|||
|
|
endRegex = "($|" + endRegex + ")";
|
|||
|
|
}
|
|||
|
|
HighlightingColor wholeSpanColor = GetColor(span, span.SpanColorReference);
|
|||
|
|
return new HighlightingSpan {
|
|||
|
|
StartExpression = CreateRegex(span, span.BeginRegex, span.BeginRegexType),
|
|||
|
|
EndExpression = CreateRegex(span, endRegex, span.EndRegexType),
|
|||
|
|
RuleSet = GetRuleSet(span, span.RuleSetReference),
|
|||
|
|
StartColor = GetColor(span, span.BeginColorReference),
|
|||
|
|
SpanColor = wholeSpanColor,
|
|||
|
|
EndColor = GetColor(span, span.EndColorReference),
|
|||
|
|
SpanColorIncludesStart = true,
|
|||
|
|
SpanColorIncludesEnd = true
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitImport(XshdImport import)
|
|||
|
|
{
|
|||
|
|
HighlightingRuleSet hrs = GetRuleSet(import, import.RuleSetReference);
|
|||
|
|
XshdRuleSet inputRuleSet;
|
|||
|
|
if (reverseRuleSetDict.TryGetValue(hrs, out inputRuleSet)) {
|
|||
|
|
// ensure the ruleset is processed before importing its members
|
|||
|
|
if (VisitRuleSet(inputRuleSet) != hrs)
|
|||
|
|
Debug.Fail("this shouldn't happen");
|
|||
|
|
}
|
|||
|
|
return hrs;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public object VisitRule(XshdRule rule)
|
|||
|
|
{
|
|||
|
|
return new HighlightingRule {
|
|||
|
|
Color = GetColor(rule, rule.ColorReference),
|
|||
|
|
Regex = CreateRegex(rule, rule.Regex, rule.RegexType)
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endregion
|
|||
|
|
|
|||
|
|
static Exception Error(XshdElement element, string message)
|
|||
|
|
{
|
|||
|
|
if (element.LineNumber > 0)
|
|||
|
|
return new HighlightingDefinitionInvalidException(
|
|||
|
|
"Error at line " + element.LineNumber + ":\n" + message);
|
|||
|
|
else
|
|||
|
|
return new HighlightingDefinitionInvalidException(message);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Dictionary<string, HighlightingRuleSet> ruleSetDict = new Dictionary<string, HighlightingRuleSet>();
|
|||
|
|
Dictionary<string, HighlightingColor> colorDict = new Dictionary<string, HighlightingColor>();
|
|||
|
|
[OptionalField]
|
|||
|
|
Dictionary<string, string> propDict = new Dictionary<string, string>();
|
|||
|
|
|
|||
|
|
public HighlightingRuleSet MainRuleSet { get; private set; }
|
|||
|
|
|
|||
|
|
public HighlightingRuleSet GetNamedRuleSet(string name)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrEmpty(name))
|
|||
|
|
return MainRuleSet;
|
|||
|
|
HighlightingRuleSet r;
|
|||
|
|
if (ruleSetDict.TryGetValue(name, out r))
|
|||
|
|
return r;
|
|||
|
|
else
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public HighlightingColor GetNamedColor(string name)
|
|||
|
|
{
|
|||
|
|
HighlightingColor c;
|
|||
|
|
if (colorDict.TryGetValue(name, out c))
|
|||
|
|
return c;
|
|||
|
|
else
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public IEnumerable<HighlightingColor> NamedHighlightingColors {
|
|||
|
|
get {
|
|||
|
|
return colorDict.Values;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override string ToString()
|
|||
|
|
{
|
|||
|
|
return this.Name;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public IDictionary<string, string> Properties {
|
|||
|
|
get {
|
|||
|
|
return propDict;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|