mirror of
https://bitbucket.org/anguist/ntpa
synced 2026-09-13 12:11:23 +00:00
461 lines
13 KiB
C#
461 lines
13 KiB
C#
//
|
|
// Copyright (c) 2013-2017 Carsten Sonne Larsen <cs@innolan.net>
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using Ntp.Analyzer.Config.Node;
|
|
using Ntp.Analyzer.Config.Syntax.Setting;
|
|
using Ntp.Analyzer.Config.Table;
|
|
|
|
namespace Ntp.Analyzer.Config.Syntax
|
|
{
|
|
public abstract class SyntaxNode<T> : ISyntaxNode
|
|
where T : ConfigurationNode
|
|
{
|
|
protected SyntaxNode(Symbol symbol, string name, int line, bool requirePath = false)
|
|
{
|
|
Symbol = symbol;
|
|
Name = name;
|
|
Line = line;
|
|
RequirePath = requirePath;
|
|
compiledNode = null;
|
|
Nodes = new List<ISyntaxNode>();
|
|
errors = new List<string>();
|
|
}
|
|
|
|
private readonly List<string> errors;
|
|
private T compiledNode;
|
|
|
|
protected List<ISyntaxNode> Nodes { get; }
|
|
|
|
protected string Name { get; }
|
|
|
|
public Symbol Symbol { get; }
|
|
|
|
public bool RequirePath { get; }
|
|
|
|
public int Line { get; }
|
|
|
|
public bool HasErrors => errors.Count != 0;
|
|
|
|
public IEnumerable<string> Errors => errors;
|
|
|
|
public ConfigurationNode CompiledNode => compiledNode;
|
|
|
|
public void Add(ISyntaxNode node)
|
|
{
|
|
Nodes.Add(node);
|
|
}
|
|
|
|
public IEnumerator<ISyntaxNode> GetEnumerator()
|
|
{
|
|
return Nodes.GetEnumerator();
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public void CompileNode()
|
|
{
|
|
Compile();
|
|
}
|
|
|
|
public void Resolve(SymbolTable table)
|
|
{
|
|
InternalResolve(table);
|
|
}
|
|
|
|
public void Validate(SymbolTable table)
|
|
{
|
|
ValidateTypes();
|
|
ValidateMandatories();
|
|
ValidateReferences(table);
|
|
}
|
|
|
|
public void Assemble(ISyntaxNode node)
|
|
{
|
|
if (compiledNode == null)
|
|
return;
|
|
|
|
compiledNode.Parent = node.CompiledNode;
|
|
node.CompiledNode.Assemble();
|
|
}
|
|
|
|
public T Compile()
|
|
{
|
|
return compiledNode ?? (compiledNode = InternalCompile());
|
|
}
|
|
|
|
protected abstract T InternalCompile();
|
|
|
|
#region Validation methods
|
|
|
|
/// <summary>
|
|
/// Override to validates the types in this syntax node.
|
|
/// </summary>
|
|
/// <remarks>When overriding this method do not call the base method.</remarks>
|
|
protected virtual void ValidateTypes()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override to validates the mandatory types in this syntax node.
|
|
/// </summary>
|
|
/// <remarks>When overriding this method do not call the base method.</remarks>
|
|
protected virtual void ValidateMandatories()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override to validates the references in this syntax node.
|
|
/// </summary>
|
|
/// <remarks>When overriding this method do not call the base method.</remarks>
|
|
/// <param name="table">Table.</param>
|
|
protected virtual void ValidateReferences(SymbolTable table)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override to resolve references to other syntax nodes from this syntax node.
|
|
/// </summary>
|
|
/// <remarks>When overriding this method do not call the base method.</remarks>
|
|
/// <param name="table">Table.</param>
|
|
protected virtual void InternalResolve(SymbolTable table)
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Validation helpers
|
|
|
|
protected void CheckTypeIs<TU>(Symbol symbol)
|
|
where TU : SettingNode, new()
|
|
{
|
|
foreach (var node in Nodes.Where(n => n.Symbol == symbol))
|
|
{
|
|
if (node is TU)
|
|
continue;
|
|
|
|
// Build error message
|
|
var b = new StringBuilder();
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append("Value of setting ");
|
|
b.Append(Keyword.Find(symbol).Name);
|
|
b.Append(" must be ");
|
|
b.Append(new TU().SettingValue);
|
|
errors.Add(b.ToString());
|
|
}
|
|
}
|
|
|
|
protected void CheckTypeIs<TU, TV>(Symbol symbol)
|
|
where TU : SettingNode, new()
|
|
where TV : SettingNode, new()
|
|
{
|
|
foreach (var node in Nodes.Where(n => n.Symbol == symbol))
|
|
{
|
|
if (node is TU || node is TV)
|
|
continue;
|
|
|
|
// Build error message
|
|
var b = new StringBuilder();
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append("Value of setting ");
|
|
b.Append(Keyword.Find(symbol).Name);
|
|
b.Append(" must be ");
|
|
b.Append(new TU().SettingValue);
|
|
b.Append(" or ");
|
|
b.Append(new TV().SettingValue);
|
|
errors.Add(b.ToString());
|
|
}
|
|
}
|
|
|
|
protected void CheckAllIsPresent(IEnumerable<Symbol> list)
|
|
{
|
|
var missing = list.
|
|
Where(item => Nodes.Count(n => n.Symbol == item) == 0).
|
|
ToList();
|
|
|
|
if (missing.Count == 0)
|
|
return;
|
|
|
|
// Build error message
|
|
int i = 0;
|
|
int count = missing.Count;
|
|
var b = new StringBuilder();
|
|
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append(Keyword.Find(Symbol).Name);
|
|
b.Append(" section is missing the setting");
|
|
b.Append(missing.Count > 1 ? "s: " : ": ");
|
|
|
|
foreach (var item in missing)
|
|
{
|
|
i++;
|
|
b.Append(Keyword.Find(item).Name);
|
|
if (i == count - 1 && count > 1)
|
|
{
|
|
b.Append(" and ");
|
|
}
|
|
else if (i != count)
|
|
{
|
|
b.Append(", ");
|
|
}
|
|
else
|
|
{
|
|
b.Append(".");
|
|
}
|
|
}
|
|
|
|
errors.Add(b.ToString());
|
|
}
|
|
|
|
protected void CheckOneIsPresent(IEnumerable<Symbol> list)
|
|
{
|
|
var symbols = list as IList<Symbol> ?? list.ToList();
|
|
var set = new HashSet<Symbol>(symbols);
|
|
|
|
if (Nodes.Any(node => set.Contains(node.Symbol)))
|
|
return;
|
|
|
|
// Build error message
|
|
int i = 0;
|
|
int count = symbols.Count;
|
|
var b = new StringBuilder();
|
|
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append(Keyword.Find(Symbol).Name);
|
|
b.Append(" section must contain at least one of the setting");
|
|
b.Append(count > 1 ? "s: " : ": ");
|
|
|
|
foreach (var item in symbols)
|
|
{
|
|
i++;
|
|
b.Append(Keyword.Find(item).Name);
|
|
if (i == count - 1)
|
|
{
|
|
b.Append(" or ");
|
|
}
|
|
else if (i != count)
|
|
{
|
|
b.Append(", ");
|
|
}
|
|
else
|
|
{
|
|
b.Append(".");
|
|
}
|
|
}
|
|
|
|
errors.Add(b.ToString());
|
|
}
|
|
|
|
protected void CheckOnlyOneIsPresent(IEnumerable<Symbol> list)
|
|
{
|
|
var symbols = list.ToList();
|
|
var set = new HashSet<Symbol>(symbols);
|
|
|
|
int a = Nodes.Count(node => set.Contains(node.Symbol));
|
|
|
|
if (a < 2)
|
|
return;
|
|
|
|
// Build error message
|
|
int i = 0;
|
|
int count = symbols.Count;
|
|
var b = new StringBuilder();
|
|
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append(Keyword.Find(Symbol).Name);
|
|
b.Append(" section can only contain one of the setting");
|
|
b.Append(count > 1 ? "s: " : ": ");
|
|
|
|
foreach (var item in symbols)
|
|
{
|
|
i++;
|
|
b.Append(Keyword.Find(item).Name);
|
|
if (i == count - 1)
|
|
{
|
|
b.Append(" or ");
|
|
}
|
|
else if (i != count)
|
|
{
|
|
b.Append(", ");
|
|
}
|
|
else
|
|
{
|
|
b.Append(".");
|
|
}
|
|
}
|
|
|
|
errors.Add(b.ToString());
|
|
}
|
|
|
|
protected void CheckIsUnique(IEnumerable<Symbol> list)
|
|
{
|
|
var nonUniques = (
|
|
from symbol in list
|
|
let c = Nodes.Count(n => n.Symbol == symbol)
|
|
where c > 1
|
|
select symbol
|
|
).ToList();
|
|
|
|
if (nonUniques.Count == 0)
|
|
return;
|
|
|
|
// Build error message
|
|
int i = 0;
|
|
int count = nonUniques.Count;
|
|
var b = new StringBuilder();
|
|
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append(Keyword.Find(Symbol).Name);
|
|
b.Append(" section has multiple occurrences of the setting");
|
|
b.Append(count > 1 ? "s: " : ": ");
|
|
|
|
foreach (var item in nonUniques)
|
|
{
|
|
i++;
|
|
b.Append(Keyword.Find(item).Name);
|
|
if (i == count - 1)
|
|
{
|
|
b.Append(" and ");
|
|
}
|
|
else if (i != count)
|
|
{
|
|
b.Append(", ");
|
|
}
|
|
else
|
|
{
|
|
b.Append(".");
|
|
}
|
|
}
|
|
|
|
errors.Add(b.ToString());
|
|
}
|
|
|
|
protected Uri CheckLink(string link, string keyword)
|
|
{
|
|
if (link == null)
|
|
return null;
|
|
|
|
Uri uri;
|
|
string text = link.TrimStart();
|
|
|
|
if ((text.StartsWith("http://") ||
|
|
text.StartsWith("https://") ||
|
|
text.StartsWith("ftp://") ||
|
|
text.StartsWith("mailto:") ||
|
|
text.StartsWith("javascript:")) &&
|
|
Uri.TryCreate(link, UriKind.Absolute, out uri))
|
|
return uri;
|
|
|
|
if (Uri.TryCreate("/" + link.TrimStart('/'), UriKind.Relative, out uri))
|
|
return uri;
|
|
|
|
if (Uri.TryCreate(link, UriKind.Absolute, out uri))
|
|
return uri;
|
|
|
|
var name = Keyword.Find(Symbol).Name;
|
|
var b = new StringBuilder();
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append($"{keyword} {name} is not a wellformed link.");
|
|
AddError(b.ToString());
|
|
|
|
return null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Error message helpers
|
|
|
|
protected void AddReferenceNameError(ISyntaxNode node, string keyword, string name)
|
|
{
|
|
var b = new StringBuilder();
|
|
if (Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(node.Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append($"{keyword} {name} refers to a nonexistent section.");
|
|
AddError(b.ToString());
|
|
}
|
|
|
|
protected void AddReferenceTypeError(ISyntaxNode node, string keyword, string section, string name)
|
|
{
|
|
var b = new StringBuilder();
|
|
if (node.Line != 0)
|
|
{
|
|
b.Append("Error in line ");
|
|
b.Append(node.Line);
|
|
b.Append(": ");
|
|
}
|
|
b.Append($"{keyword} {name} does not refer to a {section} section.");
|
|
AddError(b.ToString());
|
|
}
|
|
|
|
protected void AddError(string message)
|
|
{
|
|
errors.Add(message);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |