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
2 changes: 1 addition & 1 deletion src/generator/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sourcemeta_library(NAMESPACE sourcemeta PROJECT codegen NAME generator
FOLDER "Codegen/Generator"
PRIVATE_HEADERS typescript.h
SOURCES typescript.cc)
SOURCES typescript.cc mangle.cc)

if(CODEGEN_INSTALL)
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT codegen NAME generator)
Expand Down
13 changes: 12 additions & 1 deletion src/generator/include/sourcemeta/codegen/generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,31 @@

#include <sourcemeta/core/jsonpointer.h>

#include <map> // std::map
#include <ostream> // std::ostream
#include <string> // std::string
#include <string_view> // std::string_view
#include <variant> // std::visit
#include <vector> // std::vector

/// @defgroup generator Generator
/// @brief The codegen JSON Schema code generation package

namespace sourcemeta::codegen {

/// @ingroup generator
SOURCEMETA_CODEGEN_GENERATOR_EXPORT
auto mangle(const std::string_view prefix,
const sourcemeta::core::Pointer &pointer,
const std::vector<std::string> &symbol,
std::map<std::string, sourcemeta::core::Pointer> &cache)
-> const std::string &;

/// @ingroup generator
template <typename T>
auto generate(std::ostream &output, const IRResult &result,
const std::string_view prefix = "Schema") -> void {
const T visitor{output, prefix};
T visitor{output, prefix};
const char *separator{""};
for (const auto &entity : result) {
output << separator;
Expand Down
21 changes: 13 additions & 8 deletions src/generator/include/sourcemeta/codegen/generator_typescript.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@

#include <sourcemeta/codegen/ir.h>

#include <sourcemeta/core/jsonpointer.h>

#include <map> // std::map
#include <ostream> // std::ostream
#include <string> // std::string
#include <string_view> // std::string_view

namespace sourcemeta::codegen {
Expand All @@ -16,14 +20,14 @@ namespace sourcemeta::codegen {
class SOURCEMETA_CODEGEN_GENERATOR_EXPORT TypeScript {
public:
TypeScript(std::ostream &stream, std::string_view type_prefix);
auto operator()(const IRScalar &entry) const -> void;
auto operator()(const IREnumeration &entry) const -> void;
auto operator()(const IRObject &entry) const -> void;
auto operator()(const IRImpossible &entry) const -> void;
auto operator()(const IRArray &entry) const -> void;
auto operator()(const IRReference &entry) const -> void;
auto operator()(const IRTuple &entry) const -> void;
auto operator()(const IRUnion &entry) const -> void;
auto operator()(const IRScalar &entry) -> void;
auto operator()(const IREnumeration &entry) -> void;
auto operator()(const IRObject &entry) -> void;
auto operator()(const IRImpossible &entry) -> void;
auto operator()(const IRArray &entry) -> void;
auto operator()(const IRReference &entry) -> void;
auto operator()(const IRTuple &entry) -> void;
auto operator()(const IRUnion &entry) -> void;

private:
// Exporting symbols that depends on the standard C++ library is considered
Expand All @@ -35,6 +39,7 @@ class SOURCEMETA_CODEGEN_GENERATOR_EXPORT TypeScript {
// NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members)
std::ostream &output;
std::string_view prefix;
std::map<std::string, sourcemeta::core::Pointer> cache;
#if defined(_MSC_VER)
#pragma warning(default : 4251)
#endif
Expand Down
90 changes: 90 additions & 0 deletions src/generator/mangle.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#include <sourcemeta/codegen/generator.h>

namespace {

auto is_alpha(char character) -> bool {
return (character >= 'a' && character <= 'z') ||
(character >= 'A' && character <= 'Z');
}

auto is_digit(char character) -> bool {
return character >= '0' && character <= '9';
}

auto to_upper(char character) -> char {
if (character >= 'a' && character <= 'z') {
return static_cast<char>(character - 'a' + 'A');
}
return character;
}

auto symbol_to_identifier(const std::string_view prefix,
const std::vector<std::string> &symbol)
-> std::string {
std::string result{prefix};

for (const auto &segment : symbol) {
if (segment.empty()) {
continue;
}

bool first_in_segment{true};
for (const auto character : segment) {
if (is_alpha(character)) {
if (first_in_segment) {
result += to_upper(character);
first_in_segment = false;
} else {
result += character;
}
} else if (is_digit(character)) {
if (first_in_segment) {
result += '_';
}
result += character;
first_in_segment = false;
} else if (character == '_' || character == '$') {
result += character;
first_in_segment = false;
}
}
}

if (result.empty()) {
return "_";
}

if (is_digit(result[0])) {
result.insert(0, "_");
}

return result;
}

} // namespace

namespace sourcemeta::codegen {

auto mangle(const std::string_view prefix,
const sourcemeta::core::Pointer &pointer,
const std::vector<std::string> &symbol,
std::map<std::string, sourcemeta::core::Pointer> &cache)
-> const std::string & {
auto name{symbol_to_identifier(prefix, symbol)};

while (true) {
auto iterator{cache.find(name)};
if (iterator != cache.end()) {
if (iterator->second == pointer) {
return iterator->first;
}

name.insert(0, "_");
} else {
auto result{cache.insert({std::move(name), pointer})};
return result.first->first;
}
}
}

} // namespace sourcemeta::codegen
69 changes: 38 additions & 31 deletions src/generator/typescript.cc
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ namespace sourcemeta::codegen {
TypeScript::TypeScript(std::ostream &stream, const std::string_view type_prefix)
: output{stream}, prefix{type_prefix} {}

auto TypeScript::operator()(const IRScalar &entry) const -> void {
auto TypeScript::operator()(const IRScalar &entry) -> void {
this->output << "export type "
<< sourcemeta::core::mangle(entry.pointer, this->prefix)
<< mangle(this->prefix, entry.pointer, entry.symbol, this->cache)
<< " = ";

switch (entry.value) {
Expand All @@ -64,9 +64,9 @@ auto TypeScript::operator()(const IRScalar &entry) const -> void {
this->output << ";\n";
}

auto TypeScript::operator()(const IREnumeration &entry) const -> void {
auto TypeScript::operator()(const IREnumeration &entry) -> void {
this->output << "export type "
<< sourcemeta::core::mangle(entry.pointer, this->prefix)
<< mangle(this->prefix, entry.pointer, entry.symbol, this->cache)
<< " = ";

const char *separator{""};
Expand All @@ -79,19 +79,20 @@ auto TypeScript::operator()(const IREnumeration &entry) const -> void {
this->output << ";\n";
}

auto TypeScript::operator()(const IRObject &entry) const -> void {
const auto type_name{sourcemeta::core::mangle(entry.pointer, this->prefix)};
auto TypeScript::operator()(const IRObject &entry) -> void {
const auto type_name{
mangle(this->prefix, entry.pointer, entry.symbol, this->cache)};
const auto has_typed_additional{
std::holds_alternative<IRType>(entry.additional)};
const auto allows_any_additional{
std::holds_alternative<bool>(entry.additional) &&
std::get<bool>(entry.additional)};

if (has_typed_additional && entry.members.empty()) {
const auto &additional_type{std::get<IRType>(entry.additional)};
this->output << "export type " << type_name << " = Record<string, "
<< sourcemeta::core::mangle(
std::get<IRType>(entry.additional).pointer,
this->prefix)
<< mangle(this->prefix, additional_type.pointer,
additional_type.symbol, this->cache)
<< ">;\n";
return;
}
Expand All @@ -117,7 +118,8 @@ auto TypeScript::operator()(const IRObject &entry) const -> void {
this->output << " " << readonly_marker << "\""
<< escape_string(member_name) << "\"" << optional_marker
<< ": "
<< sourcemeta::core::mangle(member_value.pointer, this->prefix)
<< mangle(this->prefix, member_value.pointer,
member_value.symbol, this->cache)
<< ";\n";
}

Expand All @@ -135,35 +137,36 @@ auto TypeScript::operator()(const IRObject &entry) const -> void {
this->output << " // match a superset of what JSON Schema allows\n";
for (const auto &[member_name, member_value] : entry.members) {
this->output << " "
<< sourcemeta::core::mangle(member_value.pointer,
this->prefix)
<< mangle(this->prefix, member_value.pointer,
member_value.symbol, this->cache)
<< " |\n";
}

const auto &additional_type{std::get<IRType>(entry.additional)};
this->output << " "
<< sourcemeta::core::mangle(
std::get<IRType>(entry.additional).pointer,
this->prefix)
<< mangle(this->prefix, additional_type.pointer,
additional_type.symbol, this->cache)
<< " |\n";
this->output << " undefined;\n";
}

this->output << "}\n";
}

auto TypeScript::operator()(const IRImpossible &entry) const -> void {
auto TypeScript::operator()(const IRImpossible &entry) -> void {
this->output << "export type "
<< sourcemeta::core::mangle(entry.pointer, this->prefix)
<< mangle(this->prefix, entry.pointer, entry.symbol, this->cache)
<< " = never;\n";
}

auto TypeScript::operator()(const IRArray &entry) const -> void {
auto TypeScript::operator()(const IRArray &entry) -> void {
this->output << "export type "
<< sourcemeta::core::mangle(entry.pointer, this->prefix)
<< mangle(this->prefix, entry.pointer, entry.symbol, this->cache)
<< " = ";

if (entry.items.has_value()) {
this->output << sourcemeta::core::mangle(entry.items->pointer, this->prefix)
this->output << mangle(this->prefix, entry.items->pointer,
entry.items->symbol, this->cache)
<< "[]";
} else {
this->output << "unknown[]";
Expand All @@ -172,44 +175,48 @@ auto TypeScript::operator()(const IRArray &entry) const -> void {
this->output << ";\n";
}

auto TypeScript::operator()(const IRReference &entry) const -> void {
auto TypeScript::operator()(const IRReference &entry) -> void {
this->output << "export type "
<< sourcemeta::core::mangle(entry.pointer, this->prefix) << " = "
<< sourcemeta::core::mangle(entry.target.pointer, this->prefix)
<< mangle(this->prefix, entry.pointer, entry.symbol, this->cache)
<< " = "
<< mangle(this->prefix, entry.target.pointer,
entry.target.symbol, this->cache)
<< ";\n";
}

auto TypeScript::operator()(const IRTuple &entry) const -> void {
auto TypeScript::operator()(const IRTuple &entry) -> void {
this->output << "export type "
<< sourcemeta::core::mangle(entry.pointer, this->prefix)
<< mangle(this->prefix, entry.pointer, entry.symbol, this->cache)
<< " = [";

const char *separator{""};
for (const auto &item : entry.items) {
this->output << separator
<< sourcemeta::core::mangle(item.pointer, this->prefix);
<< mangle(this->prefix, item.pointer, item.symbol,
this->cache);
separator = ", ";
}

if (entry.additional.has_value()) {
this->output << separator << "..."
<< sourcemeta::core::mangle(entry.additional->pointer,
this->prefix)
<< mangle(this->prefix, entry.additional->pointer,
entry.additional->symbol, this->cache)
<< "[]";
}

this->output << "];\n";
}

auto TypeScript::operator()(const IRUnion &entry) const -> void {
auto TypeScript::operator()(const IRUnion &entry) -> void {
this->output << "export type "
<< sourcemeta::core::mangle(entry.pointer, this->prefix)
<< mangle(this->prefix, entry.pointer, entry.symbol, this->cache)
<< " =\n";

const char *separator{""};
for (const auto &value : entry.values) {
this->output << separator << " "
<< sourcemeta::core::mangle(value.pointer, this->prefix);
<< mangle(this->prefix, value.pointer, value.symbol,
this->cache);
separator = " |\n";
}

Expand Down
2 changes: 1 addition & 1 deletion src/ir/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
sourcemeta_library(NAMESPACE sourcemeta PROJECT codegen NAME ir
FOLDER "Codegen/IR"
PRIVATE_HEADERS error.h
SOURCES ir.cc ir_default_compiler.h)
SOURCES ir.cc ir_symbol.cc ir_default_compiler.h)

if(CODEGEN_INSTALL)
sourcemeta_library_install(NAMESPACE sourcemeta PROJECT codegen NAME ir)
Expand Down
8 changes: 8 additions & 0 deletions src/ir/include/sourcemeta/codegen/ir.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <cstdint> // std::uint8_t
#include <functional> // std::function
#include <optional> // std::optional, std::nullopt
#include <string> // std::string
#include <utility> // std::pair
#include <variant> // std::variant
#include <vector> // std::vector
Expand All @@ -37,6 +38,7 @@ enum class IRScalarType : std::uint8_t {
/// @ingroup ir
struct IRType {
sourcemeta::core::Pointer pointer;
std::vector<std::string> symbol;
};

/// @ingroup ir
Expand Down Expand Up @@ -116,6 +118,12 @@ auto compile(const sourcemeta::core::JSON &schema,
const std::string_view default_dialect = "",
const std::string_view default_id = "") -> IRResult;

/// @ingroup ir
SOURCEMETA_CODEGEN_IR_EXPORT
auto symbol(const sourcemeta::core::SchemaFrame &frame,
const sourcemeta::core::SchemaFrame::Location &location)
-> std::vector<std::string>;

} // namespace sourcemeta::codegen

#endif
Loading