The bridge: a schema becomes C++ - #160
Open
matt-edmondson wants to merge 2 commits into
Open
Conversation
Schema.Cpp turns a schema into a ktsu.Coder AST and hands that to CppGenerator,
which owns every question about how C++ is spelled. Nothing here writes a
brace. All three sections of Holotype's docs/generated-cpp-target.md now
generate from a .schema.json rather than from an AST built by hand, which is
what says the schema can describe the thing at all.
It lives outside the core library because it cannot ship there: ktsu.Schema
publishes net8.0 and ktsu.Coder does not. SchemaGenerator.Register is how it is
found by the language a schema names, and SchemaTool is the worked example of a
host doing it.
CppGeneratorOptions is what a target says that the schema cannot, and the list
is shorter than expected. A unit is a semantic type, so the generator emits the
class; a class is a struct; the standard library spells a string, a sequence, a
view and an absent value. What is left is what a program supplies for itself: a
fixed-shape numeric vector, a colour, an identifier with a generation, a
fallible return, a calendar date. A target that has them says how it spells them
and which header they come from. A target that has not is refused those schema
types by name, naming the option to set, rather than handed a header that will
not compile. ExistingTypes is the other direction: a semantic type the target
already hand-wrote is named rather than generated a second time beside it.
One file per element. An enum goes to namespace scope in a header of its own
rather than nested in the class that names it - the one place the output
deliberately differs from the document. The document nests it, which was right
when an enum belonged to the component that declared it; in ktsu.Schema an enum
is a top-level element any class may name, so nesting it in the one class using
it today would move the type the moment a second class used it, breaking every
caller that spelled RigidBody::BodyKind because of an edit somewhere else. Field
offsets are unaffected, so generated_compiles.cpp still holds.
Three defects found by reading the output against the document rather than by a
test going green:
- A file naming a generated type did not include the header that declares it,
so a component using an enum, a class or a semantic type did not compile.
- A float default was written as the schema holds it. C++ reads 1 as an int and
1.0 as a double, and a braced initialiser refuses either for narrowing, which
is the whole reason to brace it.
- An enum default was constructed rather than named: BodyKind{ BodyKind::Dynamic }
where the document has BodyKind::Dynamic. The schema holds a value's name, so
the generator is what qualifies it.
And two in code I had just written: a file with no namespace configured indexed
an empty array, and the banner cited a source file the schema does not remember.
Schema now keeps SourceFileName beside SourceDirectory - not serialized, for the
same reason the directory is not.
One gap the exemplar exposes is left open and asserted rather than absent:
SchemaFunction cannot say a call leaves the receiver unchanged, so the document's
const on find() is not emitted. The AST is ready for it; closing it is a property
on SchemaFunction and one line in the builder.
46 tests in Schema.Cpp.Test, 397 in Schema.Test, 201 in SchemaEditor.Test.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk
SonarCloud on #160: four findings, all in code this PR added. S1192 three times - "underlying", "value_" and "value" each appear four times across the semantic type's members, and they are one word each rather than four coincidences. The alias is what the shim calls its representation, the field is where it holds the value, and the accessor and the parameter that fills it share a name because they are the same idea from either side. Naming them says that, and says why the field has its trailing underscore: the accessor beside it is value(), so the two would otherwise collide. S3267 once - the loop over semantic types filtered inside its body, which reads as a loop over all of them. Saying Where once says what is being skipped where a reader looks for it. 46 tests in Schema.Cpp.Test and 397 in Schema.Test still pass, and the three acceptance tests are what say the generated text did not move: every one of these names appears in the exemplar's output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



All three sections of Holotype's
docs/generated-cpp-target.mdnow generate from a.schema.jsonrather than from an AST built by hand.ktsu.Coderalready had acceptance tests that built those headers as ASTs; these start one step further back, from the file a person would actually write, and so are what says the schema can describe the thing at all.Schema.Cppturns a schema into aktsu.CoderAST and hands it toCppGenerator, which owns every question about how C++ is spelled. Nothing in this project writes a brace.Where it lives, and why
It cannot ship inside
ktsu.Schema: that library publishesnet8.0andktsu.Coderdoes not.SchemaGenerator.Registeris how it is found by the language a schema names, andSchemaTool/Program.csis the worked example of a host doing it — which makes that seam load-bearing rather than hypothetical.The options record is smaller than expected
Almost everything comes from the schema. A unit is a semantic type, so the generator emits the class. A class is a struct. The standard library spells a string, a sequence, a view and an absent value.
What's left is the short list a program supplies for itself:
Vector2/Vector3/Vector4std::arraymakes the arity a type argument, and a schema's vector is one value with named components rather than a short sequence)ColorRgb/ColorRgbaHandleResultDateTimesystem_clock::time_pointis a duration since an epoch, not a calendar dateA target that has them says how it spells them and which header they come from — a generated file naming
holo::Vector3without including the header that declares it does not compile, and nothing but whoever supplied the name knows which header that is. A target that has not is refused those schema types by name, naming the option to set.ExistingTypesis the other direction: a semantic type the target already hand-wrote is named rather than generated a second time beside it, which is how the exemplar getsholo::Kilogramsinstead of a freshly emittedKilograms.One deliberate deviation from the document
An enum goes to namespace scope in a header of its own rather than nested in the struct.
The document nests it, on the grounds that two components in one file might both declare a
kind— true of the TOML front end, where an enum belonged to the component that declared it. Inktsu.Schemaan enum is a top-level element any class may name, so nesting it in the one class that uses it today would move the type the moment a second class used it, breaking every caller that spelledRigidBody::BodyKindbecause of an unrelated edit. Field offsets are unaffected, sogenerated_compiles.cppstill holds. The acceptance test asserts this difference rather than absorbing it.Three defects found by reading the output, not by a test going green
1as an int and1.0as a double, and a braced initialiser refuses either for narrowing — which is the whole reason to brace it. Now1.0f.BodyKind{ BodyKind::Dynamic }where the document hasBodyKind::Dynamic. The schema holds a value's name, so qualifying it is the generator's job.And two in code I had just written: a file with no namespace configured indexed an empty array, and the banner cited a source file the schema does not remember.
Schemanow keepsSourceFileNamebesideSourceDirectory— not serialized, for the same reason the directory is not.One gap left open, and asserted
SchemaFunctioncannot say a call leaves the receiver unchanged, so the document'sconstonfind()is not emitted. The four conventions cover whether an argument is read-only and say nothing about the receiver.AQueryCannotYetSayItLeavesTheReceiverUnchangedasserts the gap so it is visible rather than merely absent, and fails when it is closed. The AST is ready for it (FunctionDeclaration.IsReadOnlyemits the trailingconst), so closing it is a property onSchemaFunctionand one line in the builder.Testing
46 tests in
Schema.Cpp.Test— three acceptance tests against the document's sections, the complete §4 type table a row at a time, every refusal, the naming rules, and the registration seam. 397 inSchema.Test, 201 inSchemaEditor.Test. Whole solution builds with 0 warnings on bothnet10.0andnet9.0.🤖 Generated with Claude Code
https://claude.ai/code/session_01AhoPJ5AbxP8QEBNxPQYEPk
Generated by Claude Code