// -----------------------------------------------------------------------------
// file="GlRegistryReader"
// Project: StgSharp
// Copyright (c) Nitload.
// SPDX-License-Identifier: MIT
// -----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using StgSharp.GenerateGL.Tree;
namespace StgSharp.GenerateGL.Registry
{
///
/// Projects the generic XML tree into the structural OpenGL registry model.
///
internal static class GlRegistryReader
{
private static readonly IReadOnlyDictionary _emptyAttributes =
new ReadOnlyDictionary(
new Dictionary(StringComparer.Ordinal));
public static GlRegistryModel Read(
XmlTreeDocument document
)
{
ArgumentNullException.ThrowIfNull(document);
XElement root = document.Tree.Root ?? throw new InvalidDataException(
$"XML document '{document.SourcePath}' does not contain a root element.");
if (root.Name != "registry")
{
throw CreateError(
document,
root,
$"Expected , but found <{root.Name}>.");
}
ValidateChildren(
document,
root,
"comment",
"types",
"kinds",
"groups",
"enums",
"commands",
"feature",
"extensions");
return new GlRegistryModel(
document.SourcePath,
ReadOptionalElementText(root, "comment"),
ReadSection(document, root, "types", "type").Select(
value => ReadType(document, value)),
ReadSection(document, root, "kinds", "kind").Select(
value => ReadKind(document, value)),
ReadSection(document, root, "groups", "group").Select(
value => ReadEnumGroup(document, value)),
root.Elements("enums").Select(value => ReadEnumBlock(document, value)),
root.Elements("commands").Select(
value => ReadCommandBlock(document, value)),
root.Elements("feature").Select(value => ReadFeature(document, value)),
ReadSection(document, root, "extensions", "extension").Select(
value => ReadExtension(document, value)),
ReadElement(root));
}
private static GlTypeDefinition ReadType(
XmlTreeDocument document,
XElement element
)
{
string name = ReadOptionalAttribute(element, "name") ??
ReadOptionalElementText(element, "name") ??
throw CreateError(
document,
element,
"A registry requires a name attribute or child element.");
return new GlTypeDefinition(
name,
ReadDeclarationText(element),
ReadOptionalAttribute(element, "api"),
ReadOptionalAttribute(element, "requires"),
ReadOptionalAttribute(element, "type"),
ReadOptionalAttribute(element, "comment"),
ReadElement(element));
}
private static GlKindDefinition ReadKind(
XmlTreeDocument document,
XElement element
)
{
return new GlKindDefinition(
ReadRequiredAttribute(document, element, "name"),
ReadRequiredAttribute(document, element, "desc"),
ReadElement(element));
}
private static GlEnumGroup ReadEnumGroup(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "enum");
return new GlEnumGroup(
ReadRequiredAttribute(document, element, "name"),
ReadOptionalAttribute(element, "comment"),
Freeze(
element.Elements("enum").Select(
value => new GlNamedReference(
ReadRequiredAttribute(document, value, "name"),
ReadElement(value)))),
ReadElement(element));
}
private static GlEnumBlock ReadEnumBlock(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "enum", "unused");
return new GlEnumBlock(
ReadOptionalAttribute(element, "namespace"),
ReadOptionalAttribute(element, "group"),
ReadOptionalAttribute(element, "type"),
ReadOptionalAttribute(element, "start"),
ReadOptionalAttribute(element, "end"),
ReadOptionalAttribute(element, "vendor"),
ReadOptionalAttribute(element, "comment"),
Freeze(
element.Elements("enum").Select(
value => ReadEnum(document, value))),
Freeze(
element.Elements("unused").Select(
value => ReadUnusedEnumRange(document, value))),
ReadElement(element));
}
private static GlEnumDefinition ReadEnum(
XmlTreeDocument document,
XElement element
)
{
return new GlEnumDefinition(
ReadRequiredAttribute(document, element, "name"),
ReadOptionalAttribute(element, "value"),
ReadOptionalAttribute(element, "api"),
ReadOptionalAttribute(element, "type"),
ReadOptionalAttribute(element, "group"),
ReadOptionalAttribute(element, "alias"),
ReadOptionalAttribute(element, "comment"),
ReadElement(element));
}
private static GlUnusedEnumRange ReadUnusedEnumRange(
XmlTreeDocument document,
XElement element
)
{
return new GlUnusedEnumRange(
ReadRequiredAttribute(document, element, "start"),
ReadOptionalAttribute(element, "end"),
ReadOptionalAttribute(element, "vendor"),
ReadOptionalAttribute(element, "comment"),
ReadElement(element));
}
private static GlCommandBlock ReadCommandBlock(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "command");
return new GlCommandBlock(
ReadOptionalAttribute(element, "namespace"),
Freeze(
element.Elements("command").Select(
value => ReadCommand(document, value))),
ReadElement(element));
}
private static GlCommandDefinition ReadCommand(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(
document,
element,
"proto",
"param",
"alias",
"vecequiv",
"glx");
XElement prototype = element.Element("proto") ?? throw CreateError(
document,
element,
"A registry requires a child element.");
string name = ReadRequiredElementText(document, prototype, "name");
return new GlCommandDefinition(
name,
ReadReturn(document, prototype),
Freeze(
element.Elements("param").Select(
value => ReadParameter(document, value))),
Freeze(
element.Elements("alias").Select(
value => ReadNamedReference(document, value))),
Freeze(
element.Elements("vecequiv").Select(
value => ReadNamedReference(document, value))),
Freeze(
element.Elements("glx").Select(
value => ReadCommandProtocol(document, value))),
ReadOptionalAttribute(element, "comment"),
ReadElement(element));
}
private static GlReturnDefinition ReadReturn(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "ptype", "name");
_ = ReadRequiredElementText(document, element, "name");
return new GlReturnDefinition(
ReadDeclarationText(element),
ReadOptionalElementText(element, "ptype"),
ReadOptionalAttribute(element, "group"),
ReadOptionalAttribute(element, "kind"),
ReadOptionalAttribute(element, "class"),
ReadElement(element));
}
private static GlParameterDefinition ReadParameter(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "ptype", "name");
return new GlParameterDefinition(
ReadRequiredElementText(document, element, "name"),
ReadDeclarationText(element),
ReadOptionalElementText(element, "ptype"),
ReadOptionalAttribute(element, "group"),
ReadOptionalAttribute(element, "kind"),
ReadOptionalAttribute(element, "class"),
ReadOptionalAttribute(element, "len"),
ReadElement(element));
}
private static GlNamedReference ReadNamedReference(
XmlTreeDocument document,
XElement element
)
{
return new GlNamedReference(
ReadRequiredAttribute(document, element, "name"),
ReadElement(element));
}
private static GlCommandProtocol ReadCommandProtocol(
XmlTreeDocument document,
XElement element
)
{
return new GlCommandProtocol(
ReadRequiredAttribute(document, element, "type"),
ReadRequiredAttribute(document, element, "opcode"),
ReadOptionalAttribute(element, "name"),
ReadOptionalAttribute(element, "comment"),
ReadElement(element));
}
private static GlFeatureDefinition ReadFeature(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "require", "remove");
return new GlFeatureDefinition(
ReadRequiredAttribute(document, element, "name"),
ReadRequiredAttribute(document, element, "api"),
ReadRequiredAttribute(document, element, "number"),
ReadOptionalAttribute(element, "protect"),
ReadOptionalAttribute(element, "comment"),
Freeze(
element.Elements().Select(
value => ReadRequirement(document, value))),
ReadElement(element));
}
private static GlExtensionDefinition ReadExtension(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "require", "remove");
return new GlExtensionDefinition(
ReadRequiredAttribute(document, element, "name"),
ReadOptionalAttribute(element, "supported"),
ReadOptionalAttribute(element, "protect"),
ReadOptionalAttribute(element, "comment"),
Freeze(
element.Elements().Select(
value => ReadRequirement(document, value))),
ReadElement(element));
}
private static GlRequirementBlock ReadRequirement(
XmlTreeDocument document,
XElement element
)
{
ValidateChildren(document, element, "type", "enum", "command");
GlRequirementOperation operation = element.Name.LocalName switch
{
"require" => GlRequirementOperation.Require,
"remove" => GlRequirementOperation.Remove,
_ => throw CreateError(
document,
element,
$"Expected or , but found <{element.Name}>."),
};
return new GlRequirementBlock(
operation,
ReadOptionalAttribute(element, "api"),
ReadOptionalAttribute(element, "profile"),
ReadOptionalAttribute(element, "comment"),
Freeze(
element.Elements().Select(
value => ReadRegistryReference(document, value))),
ReadElement(element));
}
private static GlRegistryReference ReadRegistryReference(
XmlTreeDocument document,
XElement element
)
{
GlRegistryReferenceKind kind = element.Name.LocalName switch
{
"type" => GlRegistryReferenceKind.Type,
"enum" => GlRegistryReferenceKind.Enum,
"command" => GlRegistryReferenceKind.Command,
_ => throw CreateError(
document,
element,
$"Unsupported registry reference <{element.Name}>."),
};
return new GlRegistryReference(
kind,
ReadRequiredAttribute(document, element, "name"),
ReadOptionalAttribute(element, "comment"),
ReadElement(element));
}
private static IEnumerable ReadSection(
XmlTreeDocument document,
XElement root,
XName sectionName,
XName itemName
)
{
foreach (XElement section in root.Elements(sectionName))
{
ValidateChildren(document, section, itemName.LocalName);
foreach (XElement item in section.Elements(itemName))
{
yield return item;
}
}
}
private static string ReadRequiredAttribute(
XmlTreeDocument document,
XElement element,
XName name
)
{
string? value = ReadOptionalAttribute(element, name);
if (value is null)
{
throw CreateError(
document,
element,
$"<{element.Name}> requires the '{name}' attribute.");
}
return value;
}
private static string? ReadOptionalAttribute(
XElement element,
XName name
)
{
string? value = (string?)element.Attribute(name);
return string.IsNullOrWhiteSpace(value) ? null : value;
}
private static string ReadRequiredElementText(
XmlTreeDocument document,
XElement element,
XName name
)
{
string? value = ReadOptionalElementText(element, name);
if (value is null)
{
throw CreateError(
document,
element,
$"<{element.Name}> requires a non-empty <{name}> child element.");
}
return value;
}
private static string? ReadOptionalElementText(
XElement element,
XName name
)
{
string? value = element.Element(name)?.Value.Trim();
return string.IsNullOrWhiteSpace(value) ? null : value;
}
private static string ReadDeclarationText(
XElement element
)
{
return element.Value.Trim();
}
private static GlRegistryElement ReadElement(
XElement element
)
{
IXmlLineInfo lineInfo = element;
GlSourceLocation location = lineInfo.HasLineInfo()
? new GlSourceLocation(lineInfo.LineNumber, lineInfo.LinePosition)
: default;
IReadOnlyDictionary attributes = element.HasAttributes
? new ReadOnlyDictionary(
element.Attributes().ToDictionary(
value => value.Name.ToString(),
value => value.Value,
StringComparer.Ordinal))
: _emptyAttributes;
return new GlRegistryElement(
location,
element.ToString(SaveOptions.DisableFormatting),
attributes);
}
private static ReadOnlyCollection Freeze(
IEnumerable values
)
{
return Array.AsReadOnly(values.ToArray());
}
private static void ValidateChildren(
XmlTreeDocument document,
XElement element,
params string[] allowedNames
)
{
HashSet allowed = new HashSet(
allowedNames,
StringComparer.Ordinal);
XElement? unsupported = element.Elements().FirstOrDefault(
value => !allowed.Contains(value.Name.LocalName));
if (unsupported is not null)
{
throw CreateError(
document,
unsupported,
$"Unsupported <{unsupported.Name}> child in <{element.Name}>.");
}
}
private static InvalidDataException CreateError(
XmlTreeDocument document,
XElement element,
string message
)
{
IXmlLineInfo lineInfo = element;
string location = lineInfo.HasLineInfo()
? $"({lineInfo.LineNumber},{lineInfo.LinePosition})"
: string.Empty;
return new InvalidDataException(
$"{document.SourcePath}{location}: {message}");
}
}
}