From 0d5d3d290d91d76eb1d91356b25ca0d29cac62ce Mon Sep 17 00:00:00 2001
From: jdomnitz <380352+jdomnitz@users.noreply.github.com>
Date: Fri, 29 Nov 2024 18:37:37 -0500
Subject: [PATCH] Add a code generator to generate the models for us
---
.gitignore | 3 +-
Generator/ClassGenerator.cs | 223 +++++++++++++++++++++++++++++
Generator/DataType.cs | 29 ++++
Generator/Generator.cs | 47 ++++++
Generator/Generator.csproj | 26 ++++
Generator/StructParser.cs | 237 +++++++++++++++++++++++++++++++
Generator/Tag.cs | 49 +++++++
MatterDotNet.sln | 6 +
MatterDotNet/MatterDotNet.csproj | 17 ++-
Test/Test.csproj | 5 +-
10 files changed, 638 insertions(+), 4 deletions(-)
create mode 100644 Generator/ClassGenerator.cs
create mode 100644 Generator/DataType.cs
create mode 100644 Generator/Generator.cs
create mode 100644 Generator/Generator.csproj
create mode 100644 Generator/StructParser.cs
create mode 100644 Generator/Tag.cs
diff --git a/.gitignore b/.gitignore
index 9491a2f..8fc5ae0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -360,4 +360,5 @@ MigrationBackup/
.ionide/
# Fody - auto-generated XML schema
-FodyWeavers.xsd
\ No newline at end of file
+FodyWeavers.xsd
+/Generator/Structures
diff --git a/Generator/ClassGenerator.cs b/Generator/ClassGenerator.cs
new file mode 100644
index 0000000..25d01b7
--- /dev/null
+++ b/Generator/ClassGenerator.cs
@@ -0,0 +1,223 @@
+// MatterDotNet Copyright (C) 2024
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or any later version.
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY, without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+// See the GNU Affero General Public License for more details.
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+namespace Generator
+{
+ public static class ClassGenerator
+ {
+ private static readonly string HEADER = "// MatterDotNet Copyright (C) 2024 \n//\n// This program is free software: you can redistribute it and/or modify\n// it under the terms of the GNU Affero General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or any later version.\n// This program is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY, without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n// See the GNU Affero General Public License for more details.\n// You should have received a copy of the GNU Affero General Public License\n// along with this program. If not, see .\n//\n// WARNING: This file was auto-generated. Do not edit.\n\nusing MatterDotNet.Protocol.Parsers;\nusing MatterDotNet.Protocol.Payloads;\nusing System.Diagnostics.CodeAnalysis;\n";
+ public static bool Emit(Stream stream, Tag tag)
+ {
+ StreamWriter writer = new StreamWriter(stream);
+ {
+ writer.WriteLine(HEADER);
+ writer.WriteLine($"namespace MatterDotNet.Messages\n{{");
+ WriteTag(" ", tag, writer);
+ writer.Write("}");
+ writer.Flush();
+ }
+ return true;
+ }
+
+ private static void WriteTag(string indent, Tag tag, StreamWriter writer)
+ {
+ writer.Write($"{indent}public class {tag.Name}");
+ if (tag.Parent == null)
+ writer.WriteLine($" : TLVPayload\n{indent}{{\n{indent} /// \n{indent} public {tag.Name}() {{}}\n\n{indent} /// \n{indent} [SetsRequiredMembers]\n{indent} public {tag.Name}(Memory data) : this(new TLVReader(data)) {{}}\n");
+ else
+ writer.WriteLine($"\n{indent}{{");
+ foreach (Tag child in tag.Children)
+ {
+ if ((child.Type == DataType.Array || child.Type == DataType.List) && child.Children.Count > 0)
+ WriteTag(indent + " ", child.Children[0], writer);
+ }
+ foreach (Tag child in tag.Children)
+ {
+ if (child.Type != DataType.Structure)
+ writer.WriteLine($"{indent} {(child.Optional? "public" : "public required")} {GetType(child)}{((child.Nullable || child.Optional) ? "?" : "")} {child.Name} {{ get; set; }} ");
+ }
+ if (tag.Parent == null) {
+ writer.WriteLine($"\n{indent} /// \n{indent} [SetsRequiredMembers]\n{indent} public {tag.Name}(TLVReader reader) {{");
+ writer.WriteLine($"{indent} reader.StartStructure();");
+ foreach (Tag child in tag.Children)
+ {
+ string totalIndent = $"{indent} ";
+ if (child.Optional)
+ {
+ writer.WriteLine($"{totalIndent}if (reader.IsTag({child.TagNumber}))");
+ totalIndent += " ";
+ }
+ switch (child.Type)
+ {
+ case DataType.Boolean:
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetBool({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ break;
+ case DataType.Integer:
+ if (child.LengthBytes == 1)
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetSByte({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ else if (child.LengthBytes == 2)
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetShort({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ else if (child.LengthBytes == 4)
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetInt({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ else
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetLong({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ break;
+ case DataType.UnsignedInteger:
+ if (child.LengthBytes == 1)
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetByte({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ else if (child.LengthBytes == 2)
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetUShort({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ else if (child.LengthBytes == 4)
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetUInt({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ else
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetULong({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ break;
+ case DataType.FloatingPoint:
+ if (child.LengthBytes == 4)
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetFloat({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ else
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetDouble({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? ".Value;" : ";")}");
+ break;
+ case DataType.Bytes:
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetBytes({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? "!;" : ";")}");
+ break;
+ case DataType.String:
+ writer.WriteLine($"{totalIndent}{child.Name} = reader.GetString({child.TagNumber}{(child.Nullable ? ", true)" : ")")}{(!child.Nullable && !child.Optional ? "!;" : ";")}");
+ break;
+ case DataType.Reference:
+ writer.WriteLine($"{totalIndent}{child.Name} = new {child.ReferenceName}(reader);");
+ break;
+
+ }
+ }
+ writer.WriteLine($"{indent} }}\n\n{indent} /// \n{indent} public override void Serialize(TLVWriter writer) {{");
+ writer.WriteLine($"{indent} writer.StartStructure();");
+ foreach (Tag child in tag.Children)
+ {
+ string totalIndent = $"{indent} ";
+ if (child.Optional)
+ {
+ writer.WriteLine($"{totalIndent}if ({child.Name} != null)");
+ totalIndent += " ";
+ }
+ switch (child.Type)
+ {
+ case DataType.Boolean:
+ writer.WriteLine($"{totalIndent}writer.WriteBool({child.TagNumber}, {child.Name});");
+ break;
+ case DataType.Integer:
+ if (child.LengthBytes == 1)
+ writer.WriteLine($"{totalIndent}writer.WriteSByte({child.TagNumber}, {child.Name});");
+ else if (child.LengthBytes == 2)
+ writer.WriteLine($"{totalIndent}writer.WriteShort({child.TagNumber}, {child.Name});");
+ else if (child.LengthBytes == 4)
+ writer.WriteLine($"{totalIndent}writer.WriteInt({child.TagNumber}, {child.Name});");
+ else
+ writer.WriteLine($"{totalIndent}writer.WriteLong({child.TagNumber}, {child.Name});");
+ break;
+ case DataType.UnsignedInteger:
+ if (child.LengthBytes == 1)
+ writer.WriteLine($"{totalIndent}writer.WriteByte({child.TagNumber}, {child.Name});");
+ else if (child.LengthBytes == 2)
+ writer.WriteLine($"{totalIndent}writer.WriteUShort({child.TagNumber}, {child.Name});");
+ else if (child.LengthBytes == 4)
+ writer.WriteLine($"{totalIndent}writer.WriteUInt({child.TagNumber}, {child.Name});");
+ else
+ writer.WriteLine($"{totalIndent}writer.WriteULong({child.TagNumber}, {child.Name});");
+ break;
+ case DataType.FloatingPoint:
+ if (child.LengthBytes == 4)
+ writer.WriteLine($"{totalIndent}writer.WriteFloat({child.TagNumber}, {child.Name});");
+ else
+ writer.WriteLine($"{totalIndent}writer.WriteDouble({child.TagNumber}, {child.Name});");
+ break;
+ case DataType.Bytes:
+ writer.WriteLine($"{totalIndent}writer.WriteBytes({child.TagNumber}, {child.Name}, {child.LengthBytes});");
+ break;
+ case DataType.String:
+ writer.WriteLine($"{totalIndent}writer.WriteString({child.TagNumber}, {child.Name}, {child.LengthBytes});");
+ break;
+ case DataType.Reference:
+ writer.WriteLine($"{totalIndent}{child.Name}.Serialize(writer);");
+ break;
+
+ }
+ }
+ writer.WriteLine($"{indent} writer.EndContainer();\n{indent} }}");
+ }
+ writer.WriteLine($"{indent}}}");
+ }
+
+ private static string GetType(Tag tag)
+ {
+ switch (tag.Type)
+ {
+ case DataType.Array:
+ if (tag.Children.Count == 0)
+ return "object[]";
+ return tag.Children[0].Name + "[]";
+ case DataType.Boolean:
+ return "bool";
+ case DataType.Bytes:
+ return "byte[]";
+ case DataType.FloatingPoint:
+ if (tag.LengthBytes == 4)
+ return "float";
+ else if (tag.LengthBytes == 8)
+ return "double";
+ throw new InvalidDataException(tag.Name + " has length " + tag.LengthBytes);
+ case DataType.Integer:
+ switch (tag.LengthBytes)
+ {
+ case 1:
+ return "sbyte";
+ case 2:
+ return "short";
+ case 4:
+ return "int";
+ case 8:
+ return "long";
+ default:
+ throw new InvalidDataException(tag.Name + " has length " + tag.LengthBytes);
+ }
+ case DataType.List:
+ if (tag.Children.Count == 0)
+ return "List