skir-cc-gen 0.0.3 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,20 +7,65 @@ Official plugin for generating C++ code from [.skir](https://github.com/gepheum/
7
7
 
8
8
  Targets C++17 and higher.
9
9
 
10
- ## Installation
11
-
12
- From your project's root directory, run `npm i --save-dev skir-cc-gen`.
10
+ ## Set up
13
11
 
14
12
  In your `skir.yml` file, add the following snippet under `generators`:
15
13
  ```yaml
16
14
  - mod: skir-cc-gen
15
+ outDir: ./src/skirout
17
16
  config:
18
- writeGoogleTestHeaders: true
17
+ writeGoogleTestHeaders: true # If you use GoogleTest
18
+ ```
19
+
20
+ ## Runtime dependencies
21
+
22
+ The generated C++ code depends on the [skir client library](https://github.com/gepheum/skir-cc-gen/tree/main/client), [absl](https://abseil.io/) and optionally [GoogleTest](https://github.com/google/googletest).
23
+
24
+ ### If you use CMake
25
+
26
+ Add this to your `CMakeLists.txt`:
27
+
28
+ ```cmake
29
+ include(FetchContent)
30
+
31
+ # Should be ON if writeGoogleTestHeaders is true
32
+ option(BUILD_TESTING "Build tests" OFF)
33
+
34
+ # Abseil (required, version 20250814.0+)
35
+ FetchContent_Declare(
36
+ absl
37
+ GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
38
+ GIT_TAG 20250814.1 # Use 20250814.0 or later
39
+ )
40
+ set(ABSL_PROPAGATE_CXX_STD ON)
41
+ FetchContent_MakeAvailable(absl)
42
+
43
+ if(BUILD_TESTING)
44
+ # GoogleTest (optional - only if you use writeGoogleTestHeaders)
45
+ FetchContent_Declare(
46
+ googletest
47
+ GIT_REPOSITORY https://github.com/google/googletest.git
48
+ GIT_TAG v1.15.2 # Pick the latest tag
49
+ )
50
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
51
+ FetchContent_MakeAvailable(googletest)
52
+ endif()
53
+
54
+ # skir-client
55
+ FetchContent_Declare(
56
+ skir-client
57
+ GIT_REPOSITORY https://github.com/gepheum/skir-cc-gen.git
58
+ GIT_TAG main # Or pick a specific commit/tag
59
+ SOURCE_SUBDIR client
60
+ )
61
+ FetchContent_MakeAvailable(skir-client)
19
62
  ```
20
63
 
21
- The `npm run skir` command will now generate C++ code within the `skirout` directory.
64
+ See this [example](https://github.com/gepheum/skir-cc-example/blob/main/CMakeLists.txt).
65
+
66
+ ### If you use Bazel
22
67
 
23
- For more information, see this C++ project [example](https://github.com/gepheum/skir-cc-example).
68
+ Refer to this example [BUILD.bazel](https://github.com/gepheum/skir-cc-example/blob/main/BUILD.bazel) file.
24
69
 
25
70
  ## C++ generated code guide
26
71
 
@@ -36,6 +81,7 @@ replaced with underscores.
36
81
  ```c++
37
82
  #include "skirout/user.h"
38
83
 
84
+ using ::skirout_user::SubscriptionStatus;
39
85
  using ::skirout_user::User;
40
86
  using ::skirout_user::UserRegistry;
41
87
  ```
@@ -64,7 +110,7 @@ User jane = {
64
110
  .user_id = 43,
65
111
  };
66
112
 
67
- // ${StructName}::whole forces you to initialize all the fields of the struct.
113
+ // ${Struct}::whole forces you to initialize all the fields of the struct.
68
114
  // You will get a compile-time error if you miss one.
69
115
  User lyla = User::whole{
70
116
  .name = "Lyla Doe",
@@ -86,20 +132,22 @@ User lyla = User::whole{
86
132
 
87
133
  ```c++
88
134
 
89
- // Use skirout::${kFieldName} for constant variants.
90
- User::SubscriptionStatus john_status = skirout::kFree;
91
- User::SubscriptionStatus jane_status = skirout::kPremium;
135
+ // Use skirout::${kFieldName} or ${Enum}::${kFieldName} for constant variants.
136
+ SubscriptionStatus john_status = skirout::kFree;
137
+ SubscriptionStatus jane_status = skirout::kPremium;
138
+ SubscriptionStatus lara_status = SubscriptionStatus::kFree;
92
139
 
93
140
  // Compilation error: MONDAY is not a field of the SubscriptionStatus enum.
94
- // User::SubscriptionStatus sara_status = skirout::kMonday;
95
-
96
- // Use skirout::wrap_${field_name} for data variants.
97
- User::SubscriptionStatus jade_status =
98
- skirout::wrap_trial_start_time(absl::FromUnixMillis(1743682787000));
99
-
100
- // The ${kFieldName} and wrap_${field_name} symbols are also defined in the
101
- // generated class.
102
- User::SubscriptionStatus lara_status = User::SubscriptionStatus::kFree;
141
+ // SubscriptionStatus sara_status = skirout::kMonday;
142
+
143
+ // Use wrap_${field_name} for wrapper variants.
144
+ SubscriptionStatus jade_status =
145
+ skirout::wrap_trial(SubscriptionStatus::Trial({
146
+ .start_time = absl::FromUnixMillis(1743682787000),
147
+ }));
148
+ SubscriptionStatus roni_status = SubscriptionStatus::wrap_trial({
149
+ .start_time = absl::FromUnixMillis(1743682787000),
150
+ });
103
151
  ```
104
152
 
105
153
  ### Conditions on enums
@@ -109,28 +157,28 @@ if (john_status == skirout::kFree) {
109
157
  std::cout << "John, would you like to upgrade to premium?\n";
110
158
  }
111
159
 
112
- // Call is_${field_name}() to check if the enum holds a data variant.
113
- if (jade_status.is_trial_start_time()) {
114
- // as_${field_name}() returns the value held by the enum
115
- const absl::Time trial_start_time = jade_status.as_trial_start_time();
116
- std::cout << "Jade's trial started on " << trial_start_time << "\n";
160
+ // Call is_${field_name}() to check if the enum holds a wrapper variant.
161
+ if (jade_status.is_trial()) {
162
+ // as_${field_name}() returns the wrapped value
163
+ const SubscriptionStatus::Trial& trial = jade_status.as_trial();
164
+ std::cout << "Jade's trial started on " << trial.start_time << "\n";
117
165
  }
118
166
 
119
167
  // One way to do an exhaustive switch on an enum.
120
168
  switch (lara_status.kind()) {
121
- case User::SubscriptionStatus::kind_type::kUnknown:
169
+ case SubscriptionStatus::kind_type::kUnknown:
122
170
  // UNKNOWN is the default value for an uninitialized SubscriptionStatus.
123
171
  // ...
124
172
  break;
125
- case User::SubscriptionStatus::kind_type::kFreeConst:
173
+ case SubscriptionStatus::kind_type::kFreeConst:
126
174
  // ...
127
175
  break;
128
- case User::SubscriptionStatus::kind_type::kPremiumConst:
176
+ case SubscriptionStatus::kind_type::kPremiumConst:
129
177
  // ...
130
178
  break;
131
- case User::SubscriptionStatus::kind_type::kTrialStartTimeWrapper: {
132
- const absl::Time& trial_start_time = lara_status.as_trial_start_time();
133
- std::cout << "Lara's trial started on " << trial_start_time << "\n";
179
+ case SubscriptionStatus::kind_type::kTrialWrapper: {
180
+ const SubscriptionStatus::Trial& trial = lara_status.as_trial();
181
+ std::cout << "Lara's trial started on " << trial.start_time << "\n";
134
182
  }
135
183
  }
136
184
 
@@ -145,10 +193,9 @@ struct Visitor {
145
193
  void operator()(skirout::k_premium) const {
146
194
  std::cout << "Lara's subscription status is PREMIUM\n";
147
195
  }
148
- void operator()(
149
- User::SubscriptionStatus::wrap_trial_start_time_type& w) const {
150
- const absl::Time& trial_start_time = w.value;
151
- std::cout << "Lara's trial started on " << trial_start_time << "\n";
196
+ void operator()(SubscriptionStatus::wrap_trial_type& w) const {
197
+ const SubscriptionStatus::Trial& trial = w.value;
198
+ std::cout << "Lara's trial started on " << trial.start_time << "\n";
152
199
  }
153
200
  };
154
201
  lara_status.visit(Visitor());
@@ -212,6 +259,13 @@ assert(maybe_jane != nullptr && *maybe_jane == jane);
212
259
 
213
260
  assert(users.find_or_default(44).name == "Lyla Doe");
214
261
  assert(users.find_or_default(45).name == "");
262
+
263
+ // If multiple items have the same key, find_or_null and find_or_default
264
+ // return the last one. Duplicates are allowed but generally discouraged.
265
+ User evil_lyla = lyla;
266
+ evil_lyla.name = "Evil Lyla";
267
+ users.push_back(evil_lyla);
268
+ assert(users.find_or_default(44).name == "Evil Lyla");
215
269
  ```
216
270
 
217
271
  ### Equality and hashing
@@ -265,7 +319,7 @@ assert(reserialized_type_descriptor.ok());
265
319
  ### Static reflection
266
320
 
267
321
  Static reflection allows you to inspect and modify values of generated
268
- skir types in a typesafe maneer.
322
+ skir types in a typesafe manner.
269
323
 
270
324
  See [string_capitalizer.h](https://github.com/gepheum/skir-cc-example/blob/main/string_capitalizer.h).
271
325
 
@@ -287,10 +341,10 @@ std::cout << tarzan_copy << "\n";
287
341
  // .picture: "🐒",
288
342
  // },
289
343
  // },
290
- // .subscription_status: ::skirout::wrap_trial_start_time(absl::FromUnixMillis(1743592409000 /* 2025-04-02T11:13:29+00:00 */)),
344
+ // .subscription_status:
345
+ // ::skirout::wrap_trial_start_time(absl::FromUnixMillis(1743592409000 /*
346
+ // 2025-04-02T11:13:29+00:00 */)),
291
347
  // }
292
-
293
- // ...
294
348
  ```
295
349
 
296
350
  ### Writing unit tests with GoogleTest
@@ -323,13 +377,14 @@ EXPECT_THAT(john, (StructIs<User>{
323
377
  #### Enum matchers
324
378
 
325
379
  ```c++
326
- User::SubscriptionStatus john_status = skirout::kFree;
380
+ SubscriptionStatus john_status = skirout::kFree;
327
381
 
328
382
  EXPECT_THAT(john_status, testing::Eq(skirout::kFree));
329
383
 
330
- User::SubscriptionStatus jade_status =
331
- skirout::wrap_trial_start_time(absl::FromUnixMillis(1743682787000));
384
+ SubscriptionStatus jade_status = SubscriptionStatus::wrap_trial(
385
+ {.start_time = absl::FromUnixMillis(1743682787000)});
332
386
 
333
- EXPECT_THAT(jade_status, IsTrialStartTime());
334
- EXPECT_THAT(jade_status, IsTrialStartTime(testing::Gt(absl::UnixEpoch())));
387
+ EXPECT_THAT(jade_status, IsTrial());
388
+ EXPECT_THAT(jade_status, IsTrial(StructIs<SubscriptionStatus::Trial>{
389
+ .start_time = testing::Gt(absl::UnixEpoch())}));
335
390
  ```
@@ -0,0 +1,71 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(skir-client CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+
7
+ # ============================================================================
8
+ # Dependencies - Use existing targets if available, otherwise find them
9
+ # ============================================================================
10
+
11
+ # Abseil - check if already provided by parent project
12
+ if(NOT TARGET absl::base)
13
+ find_package(absl REQUIRED)
14
+ endif()
15
+
16
+ # GoogleTest - check if already provided by parent project
17
+ if(NOT TARGET GTest::gtest)
18
+ find_package(GTest REQUIRED)
19
+ endif()
20
+
21
+ # ============================================================================
22
+ # Main skir library
23
+ # ============================================================================
24
+
25
+ add_library(skir
26
+ skir.cc
27
+ skir.h
28
+ )
29
+
30
+ target_include_directories(skir PUBLIC
31
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
32
+ $<INSTALL_INTERFACE:include>
33
+ )
34
+
35
+ target_link_libraries(skir PUBLIC
36
+ absl::base
37
+ absl::flat_hash_map
38
+ absl::hash
39
+ absl::check
40
+ absl::die_if_null
41
+ absl::status
42
+ absl::statusor
43
+ absl::strings
44
+ absl::time
45
+ absl::optional
46
+ absl::variant
47
+ )
48
+
49
+ # ============================================================================
50
+ # Testing library (interface library with header-only implementation)
51
+ # ============================================================================
52
+
53
+ add_library(skir_testing INTERFACE)
54
+
55
+ target_sources(skir_testing INTERFACE
56
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/skir.testing.h>
57
+ $<INSTALL_INTERFACE:include/skir.testing.h>
58
+ )
59
+
60
+ target_include_directories(skir_testing INTERFACE
61
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
62
+ $<INSTALL_INTERFACE:include>
63
+ )
64
+
65
+ target_link_libraries(skir_testing INTERFACE
66
+ skir
67
+ absl::base
68
+ absl::die_if_null
69
+ GTest::gtest
70
+ GTest::gmock
71
+ )
package/client/skir.cc CHANGED
@@ -1368,7 +1368,7 @@ void Int64Adapter::Parse(ByteSource& source, int64_t& out) {
1368
1368
  ParseNumber(source, out);
1369
1369
  }
1370
1370
 
1371
- void Uint64Adapter::Append(uint64_t input, ByteSink& out) {
1371
+ void Hash64Adapter::Append(uint64_t input, ByteSink& out) {
1372
1372
  if (input < 232) {
1373
1373
  out.Push(input);
1374
1374
  } else if (input < 4294967296) {
@@ -1382,11 +1382,11 @@ void Uint64Adapter::Append(uint64_t input, ByteSink& out) {
1382
1382
  }
1383
1383
  }
1384
1384
 
1385
- void Uint64Adapter::Parse(JsonTokenizer& tokenizer, uint64_t& out) {
1385
+ void Hash64Adapter::Parse(JsonTokenizer& tokenizer, uint64_t& out) {
1386
1386
  ParseJsonNumber(tokenizer, out);
1387
1387
  }
1388
1388
 
1389
- void Uint64Adapter::Parse(ByteSource& source, uint64_t& out) {
1389
+ void Hash64Adapter::Parse(ByteSource& source, uint64_t& out) {
1390
1390
  ParseNumber(source, out);
1391
1391
  }
1392
1392
 
@@ -1730,8 +1730,8 @@ void ReflectionPrimitiveTypeAdapter::Append(
1730
1730
  out.out += "\"int64\"";
1731
1731
  break;
1732
1732
  }
1733
- case skir::reflection::PrimitiveType::kUint64: {
1734
- out.out += "\"uint64\"";
1733
+ case skir::reflection::PrimitiveType::kHash64: {
1734
+ out.out += "\"hash64\"";
1735
1735
  break;
1736
1736
  }
1737
1737
  case skir::reflection::PrimitiveType::kFloat32: {
@@ -1764,7 +1764,7 @@ void ReflectionPrimitiveTypeAdapter::Parse(
1764
1764
  {"bool", skir::reflection::PrimitiveType::kBool},
1765
1765
  {"int32", skir::reflection::PrimitiveType::kInt32},
1766
1766
  {"int64", skir::reflection::PrimitiveType::kInt64},
1767
- {"uint64", skir::reflection::PrimitiveType::kUint64},
1767
+ {"hash64", skir::reflection::PrimitiveType::kHash64},
1768
1768
  {"float32", skir::reflection::PrimitiveType::kFloat32},
1769
1769
  {"float64", skir::reflection::PrimitiveType::kFloat64},
1770
1770
  {"timestamp", skir::reflection::PrimitiveType::kTimestamp},
@@ -2098,7 +2098,7 @@ void UnrecognizedValues::ParseFrom(JsonTokenizer& tokenizer) {
2098
2098
  break;
2099
2099
  }
2100
2100
  case JsonTokenType::kUnsignedInteger: {
2101
- Uint64Adapter::Append(tokenizer.state().uint_value, bytes_);
2101
+ Hash64Adapter::Append(tokenizer.state().uint_value, bytes_);
2102
2102
  tokenizer.Next();
2103
2103
  break;
2104
2104
  }
@@ -2252,8 +2252,8 @@ void UnrecognizedValues::AppendTo(DenseJson& out) const {
2252
2252
  const uint8_t byte = *source.pos;
2253
2253
  if (byte <= 234) {
2254
2254
  uint64_t number = 0;
2255
- Uint64Adapter::Parse(source, number);
2256
- Uint64Adapter::Append(number, out);
2255
+ Hash64Adapter::Parse(source, number);
2256
+ Hash64Adapter::Append(number, out);
2257
2257
  } else {
2258
2258
  switch (static_cast<uint8_t>(byte - 235)) {
2259
2259
  case 0:
package/client/skir.h CHANGED
@@ -432,7 +432,7 @@ class keyed_items {
432
432
  kUint8,
433
433
  kUint16,
434
434
  kUint32,
435
- kUint64,
435
+ kHash64,
436
436
  };
437
437
  SlotType slot_type_ = SlotType::kUint8;
438
438
  bool being_mutated_ = false;
@@ -477,7 +477,7 @@ class keyed_items {
477
477
  case SlotType::kUint32:
478
478
  PutSlot<uint32_t>(key_hash, next_index);
479
479
  break;
480
- case SlotType::kUint64:
480
+ case SlotType::kHash64:
481
481
  PutSlot<uint64_t>(key_hash, next_index);
482
482
  break;
483
483
  }
@@ -573,7 +573,7 @@ class keyed_items {
573
573
  } else if (capacity <= std::numeric_limits<uint32_t>::max()) {
574
574
  return SlotType::kUint32;
575
575
  } else {
576
- return SlotType::kUint64;
576
+ return SlotType::kHash64;
577
577
  }
578
578
  }
579
579
 
@@ -765,7 +765,7 @@ enum class PrimitiveType {
765
765
  kBool,
766
766
  kInt32,
767
767
  kInt64,
768
- kUint64,
768
+ kHash64,
769
769
  kFloat32,
770
770
  kFloat64,
771
771
  kTimestamp,
@@ -1406,7 +1406,7 @@ struct Int64Adapter {
1406
1406
  static constexpr bool IsEnum() { return false; }
1407
1407
  };
1408
1408
 
1409
- struct Uint64Adapter {
1409
+ struct Hash64Adapter {
1410
1410
  static bool IsDefault(uint64_t input) { return !input; }
1411
1411
 
1412
1412
  template <typename Out>
@@ -1429,7 +1429,7 @@ struct Uint64Adapter {
1429
1429
  static void Parse(ByteSource& source, uint64_t& out);
1430
1430
 
1431
1431
  static skir::reflection::Type GetType(skir_type<uint64_t>) {
1432
- return skir::reflection::PrimitiveType::kUint64;
1432
+ return skir::reflection::PrimitiveType::kHash64;
1433
1433
  }
1434
1434
 
1435
1435
  static void RegisterRecords(skir_type<uint64_t>,
@@ -1603,7 +1603,7 @@ void GetAdapter(skir_type<T>) {
1603
1603
  inline BoolAdapter GetAdapter(skir_type<bool>);
1604
1604
  inline Int32Adapter GetAdapter(skir_type<int32_t>);
1605
1605
  inline Int64Adapter GetAdapter(skir_type<int64_t>);
1606
- inline Uint64Adapter GetAdapter(skir_type<uint64_t>);
1606
+ inline Hash64Adapter GetAdapter(skir_type<uint64_t>);
1607
1607
  inline Float32Adapter GetAdapter(skir_type<float>);
1608
1608
  inline Float64Adapter GetAdapter(skir_type<double>);
1609
1609
  inline TimestampAdapter GetAdapter(skir_type<absl::Time>);
@@ -2364,7 +2364,7 @@ skir::service::HttpHeaders HttplibToSkirHeaders(const HttplibHeaders& input) {
2364
2364
 
2365
2365
  template <typename MethodsTuple, std::size_t... Indices>
2366
2366
  constexpr bool unique_method_numbers_impl(std::index_sequence<Indices...>) {
2367
- constexpr std::array<int, sizeof...(Indices)> numbers = {
2367
+ constexpr std::array<int64_t, sizeof...(Indices)> numbers = {
2368
2368
  std::get<Indices>(MethodsTuple()).kNumber...};
2369
2369
  for (std::size_t i = 0; i < sizeof...(Indices); ++i) {
2370
2370
  for (std::size_t j = i + 1; j < sizeof...(Indices); ++j) {
@@ -74,7 +74,7 @@ function usePointer(type) {
74
74
  case "bool":
75
75
  case "int32":
76
76
  case "int64":
77
- case "uint64":
77
+ case "hash64":
78
78
  case "float32":
79
79
  case "float64":
80
80
  case "timestamp":
@@ -61,7 +61,7 @@ export class TypeSpeller {
61
61
  return "::int32_t";
62
62
  case "int64":
63
63
  return "::int64_t";
64
- case "uint64":
64
+ case "hash64":
65
65
  return "::uint64_t";
66
66
  case "float32":
67
67
  return "float";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skir-cc-gen",
3
- "version": "0.0.3",
3
+ "version": "1.0.1",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "lint:fix": "eslint src/**/*.ts --fix"
40
40
  },
41
41
  "dependencies": {
42
- "skir-internal": "^0.0.8",
42
+ "skir-internal": "^0.1.0",
43
43
  "zod": "^4.2.1"
44
44
  },
45
45
  "devDependencies": {
@@ -53,7 +53,7 @@
53
53
  "mocha": "^11.7.5",
54
54
  "prettier": "^3.2.4",
55
55
  "prettier-plugin-organize-imports": "^4.2.0",
56
- "skir": "^0.0.7",
56
+ "skir": "^1.0.4",
57
57
  "ts-node": "^10.9.2",
58
58
  "tsx": "^4.21.0",
59
59
  "typescript": "^5.2.2",
@@ -116,7 +116,7 @@ function usePointer(type: ResolvedType): boolean {
116
116
  case "bool":
117
117
  case "int32":
118
118
  case "int64":
119
- case "uint64":
119
+ case "hash64":
120
120
  case "float32":
121
121
  case "float64":
122
122
  case "timestamp":
@@ -73,7 +73,7 @@ export class TypeSpeller {
73
73
  return "::int32_t";
74
74
  case "int64":
75
75
  return "::int64_t";
76
- case "uint64":
76
+ case "hash64":
77
77
  return "::uint64_t";
78
78
  case "float32":
79
79
  return "float";