diff --git a/Schema.Cpp.Test/QuantityCppTests.cs b/Schema.Cpp.Test/QuantityCppTests.cs index 1ed0c07..80fed5d 100644 --- a/Schema.Cpp.Test/QuantityCppTests.cs +++ b/Schema.Cpp.Test/QuantityCppTests.cs @@ -315,6 +315,59 @@ public void ADefaultIsConstructedTheWayTheVocabularyAcceptsIt() AssertCompiles(configured, TargetOptions); } + /// + /// The table says what a quantity's bytes are, which for a vector form is not its storage. + /// + /// + /// A magnitude is one number and reports the number. A vector form is two to four of them side + /// by side, and reporting Float for one would be false about the bytes rather than + /// merely vague - false in the way that reads as true, since a consumer asks the representation + /// how many lanes a member has. Told Float, it takes a Velocity3D's first four + /// bytes for the whole value and checks one component while the other two go unexamined. + /// + /// All five arities the vocabulary has are pinned rather than the two that would carry the + /// argument. Every one of them is a real quantity - 148 magnitudes, 27 signed scalars, and 8, + /// 22 and 7 of two, three and four components - so none of these arms is defensive code, and + /// a reader of this test can see the whole rule instead of inferring it from a sample of it. + /// + /// + [TestMethod] + public void TheTableSaysWhatAQuantitysBytesAre() + { + Schema schema = new(); + SchemaClass body = schema.AddClass("Body".As())!; + + // A magnitude and a signed scalar are both one number, which is why they report the same + // thing from different arities: what the representation answers is the shape of the bytes, + // not how many directions the quantity has. + (string Member, string Quantity, string Representation)[] members = + [ + ("Mass", "Mass", "Float"), + ("Heading", "Heading", "Float"), + ("Drift", "Velocity2D", "Vector2"), + ("Velocity", "Velocity3D", "Vector3"), + ("Worldline", "Velocity4D", "Vector4"), + ]; + + foreach ((string member, string quantity, _) in members) + { + body.AddMember(member.As())! + .SetType(new Quantity { QuantityName = quantity.As() }); + } + + string table = Generate(Configured(schema), TargetOptions with { Reflection = true }) + .Single(file => file.Key.Contains("reflection", StringComparison.Ordinal)).Value; + + // Every one of them is declared a Quantity; what differs is what the bytes are. + Assert.AreEqual(members.Length, table.Split(".kind = TypeKind::Quantity").Length - 1); + + foreach ((string member, _, string representation) in members) + { + Assert.Contains($".representation = TypeKind::{representation}", table, + StringComparison.Ordinal, $"{member} should be {representation}"); + } + } + /// /// A schema of one class holding one quantity. /// diff --git a/Schema.Cpp/CppReflection.cs b/Schema.Cpp/CppReflection.cs index be77cfc..d7d64d3 100644 --- a/Schema.Cpp/CppReflection.cs +++ b/Schema.Cpp/CppReflection.cs @@ -130,13 +130,38 @@ internal static string Kind(BaseType type) => { Semantic { Declaration: SchemaSemanticType declaration } => Kind(declaration.Representation()), - // A quantity says what it is stored in directly, so there is no chain to walk: a - // Velocity3D over a float is three floats, and the table's reader needs the float. - Quantity quantity => Kind(quantity.Storage), + Quantity quantity => QuantityRepresentation(quantity), _ => Kind(type), }; + /// + /// What the bytes of a quantity are. + /// + /// + /// + /// A magnitude or a signed scalar is one number, so it is the number it is stored in. A vector + /// form is two to four of them laid out side by side, which is a Vector2, Vector3 + /// or Vector4 - and saying Float for one would be false about the bytes, not + /// merely less specific. + /// + /// + /// It is false in a way that reads as true, which is why it is worth spelling out. A consumer + /// walking the table asks the representation how many lanes a member has; told Float, it + /// takes a Velocity3D's first four bytes for the whole value and checks one component + /// against the member's range while the other two go unexamined. A check that quietly examines + /// a third of what it was given is worse than one that declines. + /// + /// + private static string QuantityRepresentation(Quantity quantity) => + quantity.Resolved?.Components switch + { + 2 => Kind(new Vector2()), + 3 => Kind(new Vector3()), + 4 => Kind(new Vector4()), + _ => Kind(quantity.Storage), + }; + /// /// How a member's interpolation is named in the table. ///