Skip to content

Commit d8d03ec

Browse files
committed
[WIP] Prototype the IR
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent 493f6c9 commit d8d03ec

File tree

4 files changed

+246
-3
lines changed

4 files changed

+246
-3
lines changed

README.markdown

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Codegen
2+
=======
3+
4+
- Each input schema corresponds to a single `.d.ts` file
5+
- 100% structurally map to TypeScript. Additional constraints might belong to
6+
validation

src/ir/include/sourcemeta/codegen/ir.h

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,58 @@
66
#endif
77

88
#include <sourcemeta/core/json.h>
9+
#include <sourcemeta/core/jsonpointer.h>
910
#include <sourcemeta/core/jsonschema.h>
1011

12+
#include <cstdint> // std::uint8_t
13+
#include <optional> // std::optional, std::nullopt
14+
#include <unordered_map> // std::unordered_map
15+
#include <variant> // std::variant
16+
#include <vector> // std::vector
17+
1118
/// @defgroup ir IR
1219
/// @brief The codegen JSON Schema intermediary format
1320

14-
namespace sourcemeta::codegen {} // namespace sourcemeta::codegen
21+
namespace sourcemeta::codegen {
22+
23+
/// @ingroup ir
24+
enum class IRScalarType : std::uint8_t { String };
25+
26+
/// @ingroup ir
27+
struct IRObjectValue {
28+
bool required;
29+
bool immutable;
30+
sourcemeta::core::PointerTemplate pointer;
31+
};
32+
33+
/// @ingroup ir
34+
struct IRScalar {
35+
sourcemeta::core::PointerTemplate pointer;
36+
IRScalarType value;
37+
};
38+
39+
/// @ingroup ir
40+
struct IRObject {
41+
sourcemeta::core::PointerTemplate pointer;
42+
std::unordered_map<sourcemeta::core::JSON::String, IRObjectValue> members;
43+
};
44+
45+
/// @ingroup ir
46+
using IREntity = std::variant<IRObject, IRScalar>;
47+
48+
/// @ingroup ir
49+
using IRResult = std::vector<IREntity>;
50+
51+
/// @ingroup ir
52+
SOURCEMETA_CODEGEN_IR_EXPORT
53+
auto compile(const sourcemeta::core::JSON &schema,
54+
const sourcemeta::core::SchemaWalker &walker,
55+
const sourcemeta::core::SchemaResolver &resolver,
56+
const std::optional<sourcemeta::core::JSON::String>
57+
&default_dialect = std::nullopt,
58+
const std::optional<sourcemeta::core::JSON::String> &default_id =
59+
std::nullopt) -> IRResult;
60+
61+
} // namespace sourcemeta::codegen
1562

1663
#endif

src/ir/ir.cc

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,140 @@
11
#include <sourcemeta/codegen/ir.h>
22

3-
namespace sourcemeta::codegen {} // namespace sourcemeta::codegen
3+
#include <algorithm> // std::ranges::sort
4+
#include <functional> // std::reference_wrapper
5+
#include <map> // std::map
6+
#include <vector> // std::vector
7+
8+
namespace sourcemeta::codegen {
9+
10+
auto compile(
11+
const sourcemeta::core::JSON &schema,
12+
const sourcemeta::core::SchemaWalker &walker,
13+
const sourcemeta::core::SchemaResolver &resolver,
14+
const std::optional<sourcemeta::core::JSON::String> &default_dialect,
15+
const std::optional<sourcemeta::core::JSON::String> &default_id)
16+
-> IRResult {
17+
sourcemeta::core::SchemaFrame frame{
18+
sourcemeta::core::SchemaFrame::Mode::Instances};
19+
frame.analyse(schema, walker, resolver, default_dialect, default_id);
20+
std::map<sourcemeta::core::PointerTemplate,
21+
std::vector<std::reference_wrapper<
22+
const sourcemeta::core::SchemaFrame::Location>>>
23+
instance_to_locations;
24+
for (const auto &[key, location] : frame.locations()) {
25+
if (location.type ==
26+
sourcemeta::core::SchemaFrame::LocationType::Resource ||
27+
location.type ==
28+
sourcemeta::core::SchemaFrame::LocationType::Subschema) {
29+
for (const auto &instance_location : frame.instance_locations(location)) {
30+
instance_to_locations[instance_location].emplace_back(
31+
std::cref(location));
32+
}
33+
}
34+
}
35+
36+
IRResult result;
37+
38+
// Process each instance location group
39+
for (const auto &[instance_location, locations] : instance_to_locations) {
40+
for (const auto &location_ref : locations) {
41+
const auto &location{location_ref.get()};
42+
const auto &subschema{sourcemeta::core::get(schema, location.pointer)};
43+
if (!subschema.is_object()) {
44+
continue;
45+
}
46+
47+
const auto vocabularies{frame.vocabularies(location, resolver)};
48+
49+
if (subschema.defines("type")) {
50+
const auto &type_result{walker("type", vocabularies)};
51+
if (type_result.type !=
52+
sourcemeta::core::SchemaKeywordType::Assertion) {
53+
continue;
54+
}
55+
56+
const auto &type_value{subschema.at("type")};
57+
if (!type_value.is_string()) {
58+
continue;
59+
}
60+
61+
const auto &type_string{type_value.to_string()};
62+
63+
if (type_string == "string") {
64+
result.emplace_back(IRScalar{.pointer = instance_location,
65+
.value = IRScalarType::String});
66+
} else if (type_string == "object") {
67+
IRObject object;
68+
object.pointer = instance_location;
69+
70+
// Find child instance locations (one property token deeper)
71+
for (const auto &[child_instance, child_locations] :
72+
instance_to_locations) {
73+
if (!child_instance.trivial() || child_instance.empty()) {
74+
continue;
75+
}
76+
77+
// Check if child is exactly one property token deeper
78+
auto child_size{
79+
std::distance(child_instance.begin(), child_instance.end())};
80+
auto parent_size{std::distance(instance_location.begin(),
81+
instance_location.end())};
82+
if (child_size != parent_size + 1) {
83+
continue;
84+
}
85+
86+
// Verify parent prefix matches
87+
auto matches{true};
88+
auto child_iter{child_instance.begin()};
89+
for (const auto &parent_token : instance_location) {
90+
if (*child_iter != parent_token) {
91+
matches = false;
92+
break;
93+
}
94+
++child_iter;
95+
}
96+
97+
if (!matches) {
98+
continue;
99+
}
100+
101+
// Get the property name from the last token
102+
const auto &last_token{*child_instance.rbegin()};
103+
if (!std::holds_alternative<sourcemeta::core::Pointer::Token>(
104+
last_token)) {
105+
continue;
106+
}
107+
108+
const auto &property_token{
109+
std::get<sourcemeta::core::Pointer::Token>(last_token)};
110+
if (!property_token.is_property()) {
111+
continue;
112+
}
113+
114+
object.members.emplace(property_token.to_property(),
115+
IRObjectValue{.required = false,
116+
.immutable = false,
117+
.pointer = child_instance});
118+
}
119+
120+
result.emplace_back(std::move(object));
121+
}
122+
}
123+
}
124+
}
125+
126+
// Sort by pointer template (longer paths come first, so dependencies
127+
// appear before their parents)
128+
const auto get_pointer{
129+
[](const auto &entry) -> const sourcemeta::core::PointerTemplate & {
130+
return entry.pointer;
131+
}};
132+
std::ranges::sort(
133+
result, [&get_pointer](const IREntity &a, const IREntity &b) -> bool {
134+
return std::visit(get_pointer, b) < std::visit(get_pointer, a);
135+
});
136+
137+
return result;
138+
}
139+
140+
} // namespace sourcemeta::codegen

test/ir/ir_test.cc

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,57 @@
22

33
#include <sourcemeta/codegen/ir.h>
44

5-
TEST(IR, test_1) { EXPECT_TRUE(true); }
5+
TEST(IR, test_1) {
6+
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
7+
"$schema": "https://json-schema.org/draft/2020-12/schema",
8+
"type": "string"
9+
})JSON")};
10+
11+
const auto result{
12+
sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker,
13+
sourcemeta::core::schema_resolver)};
14+
15+
using namespace sourcemeta::codegen;
16+
17+
EXPECT_EQ(result.size(), 1);
18+
19+
EXPECT_TRUE(std::holds_alternative<IRScalar>(result.at(0)));
20+
EXPECT_TRUE(std::get<IRScalar>(result.at(0)).pointer.empty());
21+
EXPECT_EQ(std::get<IRScalar>(result.at(0)).value, IRScalarType::String);
22+
}
23+
24+
TEST(IR, test_2) {
25+
const sourcemeta::core::JSON schema{sourcemeta::core::parse_json(R"JSON({
26+
"$schema": "https://json-schema.org/draft/2020-12/schema",
27+
"type": "object",
28+
"properties": {
29+
"foo": {
30+
"type": "string"
31+
}
32+
}
33+
})JSON")};
34+
35+
const auto result{
36+
sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker,
37+
sourcemeta::core::schema_resolver)};
38+
39+
using namespace sourcemeta::codegen;
40+
41+
EXPECT_EQ(result.size(), 2);
42+
43+
EXPECT_TRUE(std::holds_alternative<IRScalar>(result.at(0)));
44+
EXPECT_EQ(
45+
std::get<IRScalar>(result.at(0)).pointer,
46+
sourcemeta::core::PointerTemplate{sourcemeta::core::Pointer{"foo"}});
47+
EXPECT_EQ(std::get<IRScalar>(result.at(0)).value, IRScalarType::String);
48+
49+
EXPECT_TRUE(std::holds_alternative<IRObject>(result.at(1)));
50+
EXPECT_TRUE(std::get<IRObject>(result.at(1)).pointer.empty());
51+
EXPECT_EQ(std::get<IRObject>(result.at(1)).members.size(), 1);
52+
EXPECT_TRUE(std::get<IRObject>(result.at(1)).members.contains("foo"));
53+
EXPECT_FALSE(std::get<IRObject>(result.at(1)).members.at("foo").required);
54+
EXPECT_FALSE(std::get<IRObject>(result.at(1)).members.at("foo").immutable);
55+
EXPECT_EQ(
56+
std::get<IRObject>(result.at(1)).members.at("foo").pointer,
57+
sourcemeta::core::PointerTemplate{sourcemeta::core::Pointer{"foo"}});
58+
}

0 commit comments

Comments
 (0)