Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ jobs:
env:
HOMEBREW_NO_ANALYTICS: 1
HOMEBREW_NO_AUTO_UPDATE: 1
- run: npm ci
- run: cmake --version
- name: Configure Blaze (static)
if: matrix.platform.type == 'static'
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/website-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
-DBLAZE_TEST:BOOL=OFF
-DBLAZE_CONFIGURATION:BOOL=OFF
-DBLAZE_ALTERSCHEMA:BOOL=OFF
-DBLAZE_CODEGEN:BOOL=OFF
-DBLAZE_TESTS:BOOL=OFF
-DBLAZE_DOCS:BOOL=ON
- run: cmake --build ./build --config Release --target doxygen
1 change: 1 addition & 0 deletions .github/workflows/website-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
-DBLAZE_TEST:BOOL=OFF
-DBLAZE_CONFIGURATION:BOOL=OFF
-DBLAZE_ALTERSCHEMA:BOOL=OFF
-DBLAZE_CODEGEN:BOOL=OFF
-DBLAZE_TESTS:BOOL=OFF
-DBLAZE_DOCS:BOOL=ON
- run: cmake --build ./build --config Release --target doxygen
Expand Down
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ option(BLAZE_OUTPUT "Build the Blaze output formats library" ON)
option(BLAZE_TEST "Build the Blaze test runner library" ON)
option(BLAZE_CONFIGURATION "Build the Blaze configuration file library" ON)
option(BLAZE_ALTERSCHEMA "Build the Blaze alterschema rule library" ON)
option(BLAZE_CODEGEN "Build the Blaze codegen library" ON)
option(BLAZE_TESTS "Build the Blaze tests" OFF)
option(BLAZE_BENCHMARK "Build the Blaze benchmarks" OFF)
option(BLAZE_CONTRIB "Build the Blaze contrib programs" OFF)
Expand Down Expand Up @@ -67,6 +68,10 @@ if(BLAZE_ALTERSCHEMA)
add_subdirectory(src/alterschema)
endif()

if(BLAZE_CODEGEN)
add_subdirectory(src/codegen)
endif()

if(BLAZE_CONTRIB)
add_subdirectory(contrib)
endif()
Expand Down Expand Up @@ -134,6 +139,10 @@ if(BLAZE_TESTS)
add_subdirectory(test/alterschema)
endif()

if(BLAZE_CODEGEN)
add_subdirectory(test/codegen)
endif()

if(PROJECT_IS_TOP_LEVEL)
# Otherwise we need the child project to link
# against the sanitizers too.
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Programs
CMAKE = cmake
CTEST = ctest
NPM = npm

# Options
PRESET = Debug
SHARED = OFF

all: configure compile test

configure: .always
node_modules: package.json package-lock.json
$(NPM) ci

configure: node_modules .always
$(CMAKE) -S . -B ./build \
-DCMAKE_BUILD_TYPE:STRING=$(PRESET) \
-DCMAKE_COMPILE_WARNING_AS_ERROR:BOOL=ON \
Expand Down
1 change: 0 additions & 1 deletion cmake/FindCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ if(NOT Core_FOUND)
set(SOURCEMETA_CORE_INSTALL OFF CACHE BOOL "disable installation")
endif()

set(SOURCEMETA_CORE_EXTENSION_OPTIONS OFF CACHE BOOL "disable")
set(SOURCEMETA_CORE_EXTENSION_BUILD OFF CACHE BOOL "disable")

set(SOURCEMETA_CORE_CONTRIB_GOOGLETEST ${BLAZE_TESTS} CACHE BOOL "GoogleTest")
Expand Down
7 changes: 7 additions & 0 deletions config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ if(NOT BLAZE_COMPONENTS)
list(APPEND BLAZE_COMPONENTS test)
list(APPEND BLAZE_COMPONENTS configuration)
list(APPEND BLAZE_COMPONENTS alterschema)
list(APPEND BLAZE_COMPONENTS codegen)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

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 during find_package(Blaze).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At config.cmake.in, line 13:

<comment>Make the default codegen component conditional on `BLAZE_CODEGEN`; otherwise installs built without codegen will fail during `find_package(Blaze)`.</comment>

<file context>
@@ -10,6 +10,7 @@ if(NOT BLAZE_COMPONENTS)
   list(APPEND BLAZE_COMPONENTS test)
   list(APPEND BLAZE_COMPONENTS configuration)
   list(APPEND BLAZE_COMPONENTS alterschema)
+  list(APPEND BLAZE_COMPONENTS codegen)
 endif()
 
</file context>
Suggested change
list(APPEND BLAZE_COMPONENTS codegen)
if(@BLAZE_CODEGEN@)
list(APPEND BLAZE_COMPONENTS codegen)
endif()
Fix with Cubic

endif()

include(CMakeFindDependencyMacro)
Expand All @@ -35,6 +36,12 @@ foreach(component ${BLAZE_COMPONENTS})
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_compiler.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_output.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_alterschema.cmake")
elseif(component STREQUAL "codegen")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_evaluator.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_compiler.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_output.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_alterschema.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_codegen.cmake")
else()
message(FATAL_ERROR "Unknown Blaze component: ${component}")
endif()
Expand Down
9 changes: 9 additions & 0 deletions contrib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ if(BLAZE_COMPILER AND BLAZE_EVALUATOR)
endif()
endif()

if(BLAZE_CODEGEN)
sourcemeta_executable(NAMESPACE sourcemeta PROJECT blaze NAME contrib_typescript
FOLDER "Blaze/Contrib" SOURCES typescript.cc)
target_link_libraries(sourcemeta_blaze_contrib_typescript
PRIVATE sourcemeta::blaze::codegen)
target_link_libraries(sourcemeta_blaze_contrib_typescript
PRIVATE sourcemeta::core::options)
endif()

if(BLAZE_ALTERSCHEMA)
sourcemeta_executable(NAMESPACE sourcemeta PROJECT blaze
NAME contrib_canonicalize
Expand Down
75 changes: 75 additions & 0 deletions contrib/typescript.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include <sourcemeta/blaze/codegen.h>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

The 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
Check if this issue is valid — if so, understand the root cause and fix it. At contrib/typescript.cc, line 28:

<comment>Handle non-codegen failures here too; invalid options and unreadable schemas currently escape as uncaught exceptions.</comment>

<file context>
@@ -0,0 +1,75 @@
+  const std::filesystem::path schema_path{positional_arguments.front()};
+
+  try {
+    const auto schema{sourcemeta::core::read_json(schema_path)};
+
+    const auto result{
</file context>
Fix with Cubic

#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;
}
29 changes: 29 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions package.json
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"
}
}
20 changes: 20 additions & 0 deletions src/codegen/CMakeLists.txt
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)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

The 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
Check if this issue is valid — if so, understand the root cause and fix it. At src/codegen/CMakeLists.txt, line 18:

<comment>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.</comment>

<file context>
@@ -0,0 +1,20 @@
+target_link_libraries(sourcemeta_blaze_codegen PUBLIC
+  sourcemeta::core::json)
+target_link_libraries(sourcemeta_blaze_codegen PUBLIC
+  sourcemeta::core::jsonschema)
+target_link_libraries(sourcemeta_blaze_codegen PRIVATE
+  sourcemeta::blaze::alterschema)
</file context>
Fix with Cubic

target_link_libraries(sourcemeta_blaze_codegen PRIVATE
sourcemeta::blaze::alterschema)
138 changes: 138 additions & 0 deletions src/codegen/codegen.cc
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
Loading
Loading