Skip to content

Commit a53ea91

Browse files
committed
[#31]:svarga:backend, PB attribute vocabulary with .proto text emitter and clean [[pb::xxx()]] syntax
1 parent aeb3727 commit a53ea91

7 files changed

Lines changed: 1656 additions & 63 deletions

File tree

src/consumer_pb.hpp

Lines changed: 239 additions & 54 deletions
Large diffs are not rendered by default.

src/consumer_proto.hpp

Lines changed: 799 additions & 0 deletions
Large diffs are not rendered by default.

src/h5cpp.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "consumer.hpp"
2020
#include "producer_pb.hpp"
2121
#include "consumer_pb.hpp"
22+
#include "consumer_proto.hpp"
23+
#include "pb_attr_translator.hpp"
2224
#include "h5_attr_translator.hpp"
2325

2426
clang::ast_matchers::StatementMatcher h5templateMatcher = clang::ast_matchers::callExpr( clang::ast_matchers::allOf(
@@ -96,6 +98,11 @@ static llvm::cl::opt<bool> CheckMode("check",
9698
llvm::cl::cat(MyToolCategory),
9799
llvm::cl::init(false));
98100

101+
static llvm::cl::opt<std::string> ProtoOutputFile("proto-out",
102+
llvm::cl::desc("Phase 3: .proto schema output (used with --protobuf)"),
103+
llvm::cl::value_desc("file"),
104+
llvm::cl::cat(MyToolCategory));
105+
99106
int main(int argc, const char **argv) {
100107
std::cerr <<
101108
"H5CPP: Copyright (c) 2018-2026, VargaLABS, Toronto, ON Canada\n"
@@ -116,6 +123,12 @@ int main(int argc, const char **argv) {
116123
h5_attr_translator::install_virtual_files(
117124
Tool, OptionsParser.getSourcePathList(), _h5_attr_storage);
118125

126+
// Issue #31: rewrite [[pb::xxx(...)]] → [[clang::annotate("pb::xxx", ...)]]
127+
// for each source path before Clang sees it.
128+
std::vector<std::string> _pb_attr_storage;
129+
pb_attr_translator::install_virtual_files(
130+
Tool, OptionsParser.getSourcePathList(), _pb_attr_storage);
131+
119132
std::string work_path = OutputFile;
120133
if (CheckMode) {
121134
work_path = OutputFile + ".h5cpp-check";
@@ -135,6 +148,11 @@ int main(int argc, const char **argv) {
135148
PbTemplateCallback<PbProducer> callback(work_path);
136149
clang::ast_matchers::MatchFinder Finder;
137150
Finder.addMatcher(pbTemplateMatcher, &callback);
151+
std::optional<ProtoTemplateCallback> proto_cb;
152+
if (!ProtoOutputFile.empty()) {
153+
proto_cb.emplace(ProtoOutputFile);
154+
Finder.addMatcher(pbTemplateMatcher, &*proto_cb);
155+
}
138156
rc = Tool.run(clang::tooling::newFrontendActionFactory(&Finder).get());
139157
if (rc == 0 && callback.error()) rc = 1;
140158
break;

src/pb_attr_reader.hpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/* Copyright (c) 2018-2026 Steven Varga, steven@vargalabs.com Toronto, ON Canada */
2+
3+
#pragma once
4+
5+
// Commit 3 (issue #31): shared attribute readers for pb::* annotations.
6+
//
7+
// Both consumer_pb.hpp (.hpp shim emission) and consumer_proto.hpp
8+
// (.proto schema emission) used to carry their own copies of the same
9+
// AnnotateAttr-walking helpers. This header extracts the readers into
10+
// one place so adding a new backend doesn't mean copy-pasting the
11+
// boilerplate yet again.
12+
//
13+
// The user-facing rewriter (src/pb_attr_translator.hpp) lowers every
14+
// [[pb::xxx(args)]] into a [[clang::annotate("pb::xxx", args)]] before
15+
// Clang sees the source, so all backends see the same uniform shape in
16+
// the AST: a `clang::AnnotateAttr` whose first string is "pb::<kind>"
17+
// and whose trailing args are clang::Expr* nodes.
18+
//
19+
// Adding a new backend is, at the reader layer, just "include this
20+
// header." The emission layer is where each backend differs.
21+
22+
#include <clang/AST/Attr.h>
23+
#include <clang/AST/ASTContext.h>
24+
#include <clang/AST/Decl.h>
25+
#include <clang/AST/DeclCXX.h>
26+
#include <clang/AST/Expr.h>
27+
#include <llvm/ADT/SmallVector.h>
28+
#include <llvm/ADT/StringRef.h>
29+
#include <llvm/Support/Casting.h>
30+
#include <llvm/Support/raw_ostream.h>
31+
32+
#include <cstdint>
33+
#include <optional>
34+
#include <string>
35+
#include <vector>
36+
37+
namespace pb_attr_reader {
38+
39+
// Lowest-level: find the first AnnotateAttr on `d` whose annotation
40+
// string equals `kind`. Returns nullptr if none.
41+
inline const clang::AnnotateAttr*
42+
find_annotate(const clang::Decl* d, llvm::StringRef kind) {
43+
if (!d) return nullptr;
44+
for (const clang::Attr* attr : d->attrs()) {
45+
const auto* ann = llvm::dyn_cast<clang::AnnotateAttr>(attr);
46+
if (!ann) continue;
47+
if (ann->getAnnotation() == kind) return ann;
48+
}
49+
return nullptr;
50+
}
51+
52+
inline bool has_attr(const clang::Decl* d, llvm::StringRef kind) {
53+
return find_annotate(d, kind) != nullptr;
54+
}
55+
56+
// First string-literal arg in the annotation, or empty.
57+
inline std::string read_string_arg(const clang::AnnotateAttr* ann) {
58+
if (!ann) return {};
59+
for (const clang::Expr* arg : ann->args()) {
60+
if (const auto* sl =
61+
llvm::dyn_cast<clang::StringLiteral>(arg->IgnoreParenImpCasts())) {
62+
return sl->getString().str();
63+
}
64+
}
65+
return {};
66+
}
67+
68+
// All string-literal args in declaration order. Used for variadic forms
69+
// like [[pb::reserved("old_name", "older_name", ...)]].
70+
inline std::vector<std::string>
71+
read_string_args(const clang::AnnotateAttr* ann) {
72+
std::vector<std::string> out;
73+
if (!ann) return out;
74+
for (const clang::Expr* arg : ann->args()) {
75+
if (const auto* sl =
76+
llvm::dyn_cast<clang::StringLiteral>(arg->IgnoreParenImpCasts())) {
77+
out.push_back(sl->getString().str());
78+
}
79+
}
80+
return out;
81+
}
82+
83+
// All integer / enum-constant args, evaluated via Expr::EvaluateAsInt.
84+
// Used for [[pb::field(N1, N2, ...)]] (variadic on variant for oneof),
85+
// [[pb::reserved(N1, N2, ...)]], [[pb::version(N)]], etc.
86+
inline std::vector<std::uint32_t>
87+
read_int_args(const clang::AnnotateAttr* ann, clang::ASTContext& ctx) {
88+
std::vector<std::uint32_t> out;
89+
if (!ann) return out;
90+
for (const clang::Expr* arg : ann->args()) {
91+
clang::Expr::EvalResult res;
92+
if (arg->EvaluateAsInt(res, ctx)) {
93+
out.push_back(static_cast<std::uint32_t>(res.Val.getInt().getZExtValue()));
94+
}
95+
}
96+
return out;
97+
}
98+
99+
// Single integer convenience wrapper.
100+
inline std::optional<std::uint32_t>
101+
read_int_arg(const clang::AnnotateAttr* ann, clang::ASTContext& ctx) {
102+
auto v = read_int_args(ann, ctx);
103+
if (v.empty()) return std::nullopt;
104+
return v.front();
105+
}
106+
107+
// Pretty-print the first argument as source text. Used by attributes
108+
// whose argument shape is a typed expression we want to round-trip
109+
// verbatim (e.g. [[pb::on_missing(0.5)]] → "0.5"; [[pb::encode_with(
110+
// &my_fn)]] → "&my_fn"). Returns nullopt if no args.
111+
inline std::optional<std::string>
112+
read_first_arg_text(const clang::AnnotateAttr* ann, clang::ASTContext& ctx) {
113+
if (!ann || ann->args().empty()) return std::nullopt;
114+
const clang::Expr* arg = ann->args().begin()[0];
115+
std::string out;
116+
llvm::raw_string_ostream os(out);
117+
arg->printPretty(os, nullptr, clang::PrintingPolicy{ctx.getLangOpts()});
118+
return out;
119+
}
120+
121+
// ── Convenience wrappers that compose find_annotate + read_* ──────────
122+
123+
inline std::string
124+
read_field_string(const clang::FieldDecl* fld, llvm::StringRef kind) {
125+
return read_string_arg(find_annotate(fld, kind));
126+
}
127+
inline std::string
128+
read_class_string(const clang::CXXRecordDecl* rec, llvm::StringRef kind) {
129+
return read_string_arg(find_annotate(rec, kind));
130+
}
131+
inline std::string
132+
read_enum_string(const clang::EnumDecl* ed, llvm::StringRef kind) {
133+
return read_string_arg(find_annotate(ed, kind));
134+
}
135+
inline std::vector<std::uint32_t>
136+
read_class_ints(const clang::CXXRecordDecl* rec, llvm::StringRef kind) {
137+
return read_int_args(find_annotate(rec, kind), rec->getASTContext());
138+
}
139+
inline std::vector<std::string>
140+
read_class_strings(const clang::CXXRecordDecl* rec, llvm::StringRef kind) {
141+
return read_string_args(find_annotate(rec, kind));
142+
}
143+
inline std::optional<std::uint32_t>
144+
read_class_int(const clang::CXXRecordDecl* rec, llvm::StringRef kind) {
145+
return read_int_arg(find_annotate(rec, kind), rec->getASTContext());
146+
}
147+
inline std::vector<std::uint32_t>
148+
read_field_ints(const clang::FieldDecl* fld, llvm::StringRef kind) {
149+
return read_int_args(find_annotate(fld, kind), fld->getASTContext());
150+
}
151+
152+
} // namespace pb_attr_reader

0 commit comments

Comments
 (0)