Step 634: schema to C++ generator

This commit is contained in:
Bill
2026-02-17 21:37:36 -07:00
parent 98eae97f24
commit 9130277713
4 changed files with 285 additions and 0 deletions

View File

@@ -4585,4 +4585,13 @@ target_link_libraries(step633_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step634_test tests/step634_test.cpp)
target_include_directories(step634_test PRIVATE src)
target_link_libraries(step634_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -0,0 +1,116 @@
#pragma once
// Step 634: Job schema -> typed C++ structs generator
#include <nlohmann/json.hpp>
#include <cctype>
#include <string>
#include <vector>
using json = nlohmann::json;
struct SchemaToCppOutput {
bool success = false;
std::string headerCode;
std::string cmakeInterfaceTarget;
std::vector<std::string> errors;
};
class SchemaToCppGenerator {
public:
static SchemaToCppOutput generate(const json& schema,
const std::string& headerName,
const std::string& targetName) {
SchemaToCppOutput out;
if (!schema.is_object()) {
out.errors.push_back("schema_invalid");
return out;
}
if (!schema.contains("title") ||
!schema["title"].is_string() ||
schema["title"].get<std::string>().empty()) {
out.errors.push_back("schema_title_missing");
return out;
}
const std::string structName = sanitizeIdentifier(
schema["title"].get<std::string>());
if (structName.empty()) {
out.errors.push_back("schema_title_missing");
return out;
}
std::string code;
code += "#pragma once\n";
code += "#include <nlohmann/json.hpp>\n";
code += "#include <string>\n\n";
code += "struct " + structName + " {\n";
std::vector<std::pair<std::string, std::string>> fields;
if (schema.contains("properties") && schema["properties"].is_object()) {
for (auto it = schema["properties"].begin(); it != schema["properties"].end(); ++it) {
std::string fieldName = sanitizeIdentifier(it.key());
std::string cppType = mapType(it.value().value("type", "string"));
if (fieldName.empty()) continue;
fields.push_back({fieldName, cppType});
code += " " + cppType + " " + fieldName + ";\n";
}
}
if (fields.empty()) {
fields.push_back({"raw", "std::string"});
code += " std::string raw;\n";
}
code += "};\n\n";
code += "inline void to_json(nlohmann::json& j, const " + structName + "& v) {\n";
code += " j = nlohmann::json::object();\n";
for (const auto& field : fields) {
code += " j[\"" + field.first + "\"] = v." + field.first + ";\n";
}
code += "}\n\n";
code += "inline void from_json(const nlohmann::json& j, " + structName + "& v) {\n";
for (const auto& field : fields) {
code += " if (j.contains(\"" + field.first + "\")) j.at(\"" + field.first + "\").get_to(v." + field.first + ");\n";
}
code += "}\n\n";
code += "inline bool validate" + structName + "(const " + structName + "& v) {\n";
code += " (void)v;\n";
code += " return true;\n";
code += "}\n";
out.success = true;
out.headerCode = code;
out.cmakeInterfaceTarget =
"add_library(" + targetName + " INTERFACE)\n"
"target_include_directories(" + targetName + " INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)\n"
"target_sources(" + targetName + " INTERFACE include/" + headerName + ")\n";
return out;
}
static std::string toolName() {
return "whetstone_schema_to_cpp";
}
private:
static std::string mapType(const std::string& type) {
if (type == "integer") return "int";
if (type == "number") return "double";
if (type == "boolean") return "bool";
return "std::string";
}
static std::string sanitizeIdentifier(const std::string& raw) {
std::string out;
out.reserve(raw.size());
for (char ch : raw) {
if (std::isalnum(static_cast<unsigned char>(ch))) out.push_back(ch);
else if (ch == '_' || ch == '-') out.push_back('_');
}
if (out.empty()) return out;
if (std::isdigit(static_cast<unsigned char>(out.front()))) {
out = "_" + out;
}
return out;
}
};

View File

@@ -0,0 +1,128 @@
// Step 634: Job schema -> typed C++ structs generator (12 tests)
#include "SchemaToCppGenerator.h"
#include <iostream>
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; }
void test_tool_name_matches_spec() {
TEST(tool_name_matches_spec);
CHECK(SchemaToCppGenerator::toolName() == "whetstone_schema_to_cpp", "tool name mismatch");
PASS();
}
void test_generate_rejects_non_object_schema() {
TEST(generate_rejects_non_object_schema);
auto out = SchemaToCppGenerator::generate(json::array(), "Job.h", "job_schema_types");
CHECK(!out.success, "non-object should fail");
CHECK(!out.errors.empty(), "error expected");
PASS();
}
void test_generate_rejects_missing_title() {
TEST(generate_rejects_missing_title);
json schema = {{"title", ""}, {"properties", json::object()}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(!out.success, "missing title should fail");
PASS();
}
void test_generate_emits_struct_name_from_title() {
TEST(generate_emits_struct_name_from_title);
json schema = {{"title", "JobPayload"}, {"properties", json::object()}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.success, "generate should pass");
CHECK(out.headerCode.find("struct JobPayload") != std::string::npos, "struct missing");
PASS();
}
void test_generate_maps_integer_to_int() {
TEST(generate_maps_integer_to_int);
json schema = {{"title", "JobPayload"}, {"properties", {{"priority", {{"type", "integer"}}}}}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.headerCode.find("int priority;") != std::string::npos, "int field missing");
PASS();
}
void test_generate_maps_boolean_to_bool() {
TEST(generate_maps_boolean_to_bool);
json schema = {{"title", "JobPayload"}, {"properties", {{"urgent", {{"type", "boolean"}}}}}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.headerCode.find("bool urgent;") != std::string::npos, "bool field missing");
PASS();
}
void test_generate_defaults_unknown_type_to_string() {
TEST(generate_defaults_unknown_type_to_string);
json schema = {{"title", "JobPayload"}, {"properties", {{"payload", {{"type", "object"}}}}}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.headerCode.find("std::string payload;") != std::string::npos, "string fallback missing");
PASS();
}
void test_generate_emits_to_json_and_from_json() {
TEST(generate_emits_to_json_and_from_json);
json schema = {{"title", "JobPayload"}, {"properties", {{"id", {{"type", "string"}}}}}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.headerCode.find("to_json") != std::string::npos, "to_json missing");
CHECK(out.headerCode.find("from_json") != std::string::npos, "from_json missing");
PASS();
}
void test_generate_emits_validation_stub() {
TEST(generate_emits_validation_stub);
json schema = {{"title", "JobPayload"}, {"properties", {{"id", {{"type", "string"}}}}}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.headerCode.find("validateJobPayload") != std::string::npos, "validate function missing");
PASS();
}
void test_generate_emits_interface_target_text() {
TEST(generate_emits_interface_target_text);
json schema = {{"title", "JobPayload"}, {"properties", json::object()}};
auto out = SchemaToCppGenerator::generate(schema, "JobPayload.h", "job_schema_types");
CHECK(out.cmakeInterfaceTarget.find("add_library(job_schema_types INTERFACE)") != std::string::npos,
"interface target missing");
PASS();
}
void test_generate_sanitizes_hyphenated_field_name() {
TEST(generate_sanitizes_hyphenated_field_name);
json schema = {{"title", "JobPayload"}, {"properties", {{"job-id", {{"type", "string"}}}}}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.headerCode.find("job_id") != std::string::npos, "sanitized field missing");
PASS();
}
void test_generate_inserts_default_field_when_properties_empty() {
TEST(generate_inserts_default_field_when_properties_empty);
json schema = {{"title", "JobPayload"}, {"properties", json::object()}};
auto out = SchemaToCppGenerator::generate(schema, "Job.h", "job_schema_types");
CHECK(out.headerCode.find("std::string raw;") != std::string::npos, "raw fallback field missing");
PASS();
}
int main() {
std::cout << "Step 634: Job schema -> typed C++ structs generator\n";
test_tool_name_matches_spec();
test_generate_rejects_non_object_schema();
test_generate_rejects_missing_title();
test_generate_emits_struct_name_from_title();
test_generate_maps_integer_to_int();
test_generate_maps_boolean_to_bool();
test_generate_defaults_unknown_type_to_string();
test_generate_emits_to_json_and_from_json();
test_generate_emits_validation_stub();
test_generate_emits_interface_target_text();
test_generate_sanitizes_hyphenated_field_name();
test_generate_inserts_default_field_when_properties_empty();
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -12825,3 +12825,35 @@ single-function line limit.
- `editor/src/AgentTaskStatusOverlay.h` (`79` <= `600`)
- `editor/tests/step633_test.cpp` within test-file size guidance (`109` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
### Step 634: Job schema -> typed C++ structs generator
**Status:** PASS (12/12 tests)
Introduces a schema-to-C++ generator module for shared HiveMind job payload
types with emitted `to_json`/`from_json` helpers, validation stub, and CMake
INTERFACE target text.
**Files added:**
- `editor/src/SchemaToCppGenerator.h` - schema conversion module:
- schema validation and title/field sanitization
- C++ struct emission with JSON adapter helpers
- interface-target CMake snippet generation
- `editor/tests/step634_test.cpp` - 12 tests covering:
- tool identity and invalid-input rejection
- field type mapping and identifier sanitization
- JSON adapter + validation emission
- empty-properties edge behavior and interface-target text
**Files modified:**
- `editor/CMakeLists.txt` - `step634_test` target
**Verification run:**
- `cmake -S editor -B editor/build-native` - PASS
- `cmake --build editor/build-native --target step634_test step633_test` - PASS
- `./editor/build-native/step634_test` - PASS (12/12)
- `./editor/build-native/step633_test` - PASS (8/8) regression coverage
**Architecture gate check:**
- `editor/src/SchemaToCppGenerator.h` (`116` <= `600`)
- `editor/tests/step634_test.cpp` within test-file size guidance (`128` lines)
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`