-
-
Notifications
You must be signed in to change notification settings - Fork 15
Merge the Codegen project into Blaze #739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| #include <sourcemeta/blaze/codegen.h> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Handle non-codegen failures here too; invalid options and unreadable schemas currently escape as uncaught exceptions. Prompt for AI agents |
||
| #include <sourcemeta/blaze/codegen_error.h> | ||
|
|
||
| #include <sourcemeta/core/json.h> | ||
| #include <sourcemeta/core/jsonschema.h> | ||
| #include <sourcemeta/core/options.h> | ||
|
|
||
| #include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE | ||
| #include <filesystem> // std::filesystem::path | ||
| #include <iostream> // std::cout, std::cerr | ||
| #include <sstream> // std::ostringstream | ||
| #include <string> // std::string | ||
|
|
||
| auto main(int argc, char *argv[]) -> int { | ||
| sourcemeta::core::Options options; | ||
| options.option("default-prefix", {"p"}); | ||
| options.parse(argc, argv); | ||
|
|
||
| const auto &positional_arguments{options.positional()}; | ||
| if (positional_arguments.empty()) { | ||
| std::cerr << "error: missing schema path\n"; | ||
| return EXIT_FAILURE; | ||
| } | ||
|
|
||
| const std::filesystem::path schema_path{positional_arguments.front()}; | ||
|
|
||
| try { | ||
| const auto schema{sourcemeta::core::read_json(schema_path)}; | ||
|
|
||
| const auto result{ | ||
| sourcemeta::blaze::compile(schema, sourcemeta::core::schema_walker, | ||
| sourcemeta::core::schema_resolver, | ||
| sourcemeta::blaze::default_compiler)}; | ||
|
|
||
| const std::string prefix{ | ||
| options.contains("default-prefix") | ||
| ? std::string{options.at("default-prefix").front()} | ||
| : "Schema"}; | ||
|
|
||
| sourcemeta::blaze::generate<sourcemeta::blaze::TypeScript>(std::cout, | ||
| result, prefix); | ||
| } catch (const sourcemeta::blaze::CodegenUnsupportedKeywordError &error) { | ||
| std::ostringstream pointer; | ||
| sourcemeta::core::stringify(error.pointer(), pointer); | ||
| std::cerr << "error: " << error.what() << "\n"; | ||
| std::cerr << " keyword: " << error.keyword() << "\n"; | ||
| std::cerr << " location: " << pointer.str() << "\n"; | ||
| std::cerr << " schema: "; | ||
| sourcemeta::core::prettify(error.json(), std::cerr); | ||
| std::cerr << "\n"; | ||
| return EXIT_FAILURE; | ||
| } catch ( | ||
| const sourcemeta::blaze::CodegenUnsupportedKeywordValueError &error) { | ||
| std::ostringstream pointer; | ||
| sourcemeta::core::stringify(error.pointer(), pointer); | ||
| std::cerr << "error: " << error.what() << "\n"; | ||
| std::cerr << " keyword: " << error.keyword() << "\n"; | ||
| std::cerr << " location: " << pointer.str() << "\n"; | ||
| std::cerr << " schema: "; | ||
| sourcemeta::core::prettify(error.json(), std::cerr); | ||
| std::cerr << "\n"; | ||
| return EXIT_FAILURE; | ||
| } catch (const sourcemeta::blaze::CodegenUnexpectedSchemaError &error) { | ||
| std::ostringstream pointer; | ||
| sourcemeta::core::stringify(error.pointer(), pointer); | ||
| std::cerr << "error: " << error.what() << "\n"; | ||
| std::cerr << " location: " << pointer.str() << "\n"; | ||
| std::cerr << " schema: "; | ||
| sourcemeta::core::prettify(error.json(), std::cerr); | ||
| std::cerr << "\n"; | ||
| return EXIT_FAILURE; | ||
| } | ||
|
|
||
| return EXIT_SUCCESS; | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "name": "@sourcemeta/blaze", | ||
| "version": "0.0.1", | ||
| "private": true, | ||
| "devDependencies": { | ||
| "typescript": "^5.9.3" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME codegen | ||
| FOLDER "Blaze/Codegen" | ||
| PRIVATE_HEADERS error.h typescript.h | ||
| SOURCES | ||
| codegen.cc | ||
| codegen_symbol.cc | ||
| codegen_default_compiler.h | ||
| codegen_typescript.cc | ||
| codegen_mangle.cc) | ||
|
|
||
| if(BLAZE_INSTALL) | ||
| sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME codegen) | ||
| endif() | ||
|
|
||
| target_link_libraries(sourcemeta_blaze_codegen PUBLIC | ||
| sourcemeta::core::json) | ||
| target_link_libraries(sourcemeta_blaze_codegen PUBLIC | ||
| sourcemeta::core::jsonschema) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Add the missing core::regex/core::uri link dependencies; this target's sources use those symbols directly, but the CMake file only links json/jsonschema/alterschema. Prompt for AI agents |
||
| target_link_libraries(sourcemeta_blaze_codegen PRIVATE | ||
| sourcemeta::blaze::alterschema) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| #include <sourcemeta/blaze/alterschema.h> | ||
| #include <sourcemeta/blaze/codegen.h> | ||
|
|
||
| #include <algorithm> // std::ranges::sort | ||
| #include <cassert> // assert | ||
| #include <unordered_set> // std::unordered_set | ||
|
|
||
| #include "codegen_default_compiler.h" | ||
|
|
||
| namespace { | ||
|
|
||
| auto is_validation_subschema( | ||
| const sourcemeta::core::SchemaFrame &frame, | ||
| const sourcemeta::core::SchemaFrame::Location &location, | ||
| const sourcemeta::core::SchemaWalker &walker, | ||
| const sourcemeta::core::SchemaResolver &resolver) -> bool { | ||
| if (!location.parent.has_value()) { | ||
| return false; | ||
| } | ||
|
|
||
| const auto &parent{location.parent.value()}; | ||
| if (parent.size() >= location.pointer.size()) { | ||
| return false; | ||
| } | ||
|
|
||
| const auto &keyword_token{location.pointer.at(parent.size())}; | ||
| if (!keyword_token.is_property()) { | ||
| return false; | ||
| } | ||
|
|
||
| const auto parent_location{frame.traverse(parent)}; | ||
| if (!parent_location.has_value()) { | ||
| return false; | ||
| } | ||
|
|
||
| const auto vocabularies{ | ||
| frame.vocabularies(parent_location.value().get(), resolver)}; | ||
| const auto &walker_result{walker(keyword_token.to_property(), vocabularies)}; | ||
| using Type = sourcemeta::core::SchemaKeywordType; | ||
| if (walker_result.type == Type::ApplicatorValueTraverseAnyPropertyKey || | ||
| walker_result.type == Type::ApplicatorValueTraverseAnyItem) { | ||
| return true; | ||
| } | ||
|
|
||
| return is_validation_subschema(frame, parent_location.value().get(), walker, | ||
| resolver); | ||
| } | ||
|
|
||
| } // anonymous namespace | ||
|
|
||
| namespace sourcemeta::blaze { | ||
|
|
||
| auto compile(const sourcemeta::core::JSON &input, | ||
| const sourcemeta::core::SchemaWalker &walker, | ||
| const sourcemeta::core::SchemaResolver &resolver, | ||
| const CodegenCompiler &compiler, | ||
| const std::string_view default_dialect, | ||
| const std::string_view default_id) -> CodegenIRResult { | ||
| // -------------------------------------------------------------------------- | ||
| // (1) Bundle the schema to resolve external references | ||
| // -------------------------------------------------------------------------- | ||
|
|
||
| auto schema{sourcemeta::core::bundle(input, walker, resolver, default_dialect, | ||
| default_id)}; | ||
|
|
||
| // -------------------------------------------------------------------------- | ||
| // (2) Canonicalize the schema for easier analysis | ||
| // -------------------------------------------------------------------------- | ||
|
|
||
| sourcemeta::blaze::SchemaTransformer canonicalizer; | ||
| sourcemeta::blaze::add(canonicalizer, | ||
| sourcemeta::blaze::AlterSchemaMode::Canonicalizer); | ||
| [[maybe_unused]] const auto canonicalized{canonicalizer.apply( | ||
| schema, walker, resolver, | ||
| [](const auto &, const auto, const auto, const auto &, | ||
| [[maybe_unused]] const auto applied) { assert(applied); }, | ||
| default_dialect, default_id)}; | ||
| assert(canonicalized.first); | ||
|
|
||
| // -------------------------------------------------------------------------- | ||
| // (3) Frame the resulting schema with instance location information | ||
| // -------------------------------------------------------------------------- | ||
|
|
||
| sourcemeta::core::SchemaFrame frame{ | ||
| sourcemeta::core::SchemaFrame::Mode::References}; | ||
| frame.analyse(schema, walker, resolver, default_dialect, default_id); | ||
|
|
||
| // -------------------------------------------------------------------------- | ||
| // (4) Convert every subschema into a code generation object | ||
| // -------------------------------------------------------------------------- | ||
|
|
||
| std::unordered_set<sourcemeta::core::WeakPointer, | ||
| sourcemeta::core::WeakPointer::Hasher, | ||
| sourcemeta::core::WeakPointer::Comparator> | ||
| visited; | ||
| CodegenIRResult result; | ||
| for (const auto &[key, location] : frame.locations()) { | ||
| if (location.type != | ||
| sourcemeta::core::SchemaFrame::LocationType::Resource && | ||
| location.type != | ||
| sourcemeta::core::SchemaFrame::LocationType::Subschema) { | ||
| continue; | ||
| } | ||
|
|
||
| // Framing may report resource twice or more given default identifiers and | ||
| // nested resources | ||
| const auto [visited_iterator, inserted] = visited.insert(location.pointer); | ||
| if (!inserted) { | ||
| continue; | ||
| } | ||
|
|
||
| // Skip subschemas under validation-only keywords that do not contribute | ||
| // to the type structure (like `contains`) | ||
| if (is_validation_subschema(frame, location, walker, resolver)) { | ||
| continue; | ||
| } | ||
|
|
||
| const auto &subschema{sourcemeta::core::get(schema, location.pointer)}; | ||
| result.push_back(compiler(schema, frame, location, resolver, subschema)); | ||
| } | ||
|
|
||
| // -------------------------------------------------------------------------- | ||
| // (5) Sort entries so that dependencies come before dependents | ||
| // -------------------------------------------------------------------------- | ||
|
|
||
| std::ranges::sort( | ||
| result, | ||
| [](const CodegenIREntity &left, const CodegenIREntity &right) -> bool { | ||
| return std::visit([](const auto &entry) { return entry.pointer; }, | ||
| right) < | ||
| std::visit([](const auto &entry) { return entry.pointer; }, | ||
| left); | ||
| }); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| } // namespace sourcemeta::blaze |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Make the default codegen component conditional on
BLAZE_CODEGEN; otherwise installs built without codegen will fail duringfind_package(Blaze).Prompt for AI agents