IncludeCPP 3.7.3__py3-none-any.whl
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.
Potentially problematic release.
This version of IncludeCPP might be problematic. Click here for more details.
- includecpp/__init__.py +59 -0
- includecpp/__init__.pyi +255 -0
- includecpp/__main__.py +4 -0
- includecpp/cli/__init__.py +4 -0
- includecpp/cli/commands.py +8270 -0
- includecpp/cli/config_parser.py +127 -0
- includecpp/core/__init__.py +19 -0
- includecpp/core/ai_integration.py +2132 -0
- includecpp/core/build_manager.py +2416 -0
- includecpp/core/cpp_api.py +376 -0
- includecpp/core/cpp_api.pyi +95 -0
- includecpp/core/cppy_converter.py +3448 -0
- includecpp/core/cssl/CSSL_DOCUMENTATION.md +2075 -0
- includecpp/core/cssl/__init__.py +42 -0
- includecpp/core/cssl/cssl_builtins.py +2271 -0
- includecpp/core/cssl/cssl_builtins.pyi +1393 -0
- includecpp/core/cssl/cssl_events.py +621 -0
- includecpp/core/cssl/cssl_modules.py +2803 -0
- includecpp/core/cssl/cssl_parser.py +2575 -0
- includecpp/core/cssl/cssl_runtime.py +3051 -0
- includecpp/core/cssl/cssl_syntax.py +488 -0
- includecpp/core/cssl/cssl_types.py +1512 -0
- includecpp/core/cssl_bridge.py +882 -0
- includecpp/core/cssl_bridge.pyi +488 -0
- includecpp/core/error_catalog.py +802 -0
- includecpp/core/error_formatter.py +1016 -0
- includecpp/core/exceptions.py +97 -0
- includecpp/core/path_discovery.py +77 -0
- includecpp/core/project_ui.py +3370 -0
- includecpp/core/settings_ui.py +326 -0
- includecpp/generator/__init__.py +1 -0
- includecpp/generator/parser.cpp +1903 -0
- includecpp/generator/parser.h +281 -0
- includecpp/generator/type_resolver.cpp +363 -0
- includecpp/generator/type_resolver.h +68 -0
- includecpp/py.typed +0 -0
- includecpp/templates/cpp.proj.template +18 -0
- includecpp/vscode/__init__.py +1 -0
- includecpp/vscode/cssl/__init__.py +1 -0
- includecpp/vscode/cssl/language-configuration.json +38 -0
- includecpp/vscode/cssl/package.json +50 -0
- includecpp/vscode/cssl/snippets/cssl.snippets.json +1080 -0
- includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +341 -0
- includecpp-3.7.3.dist-info/METADATA +1076 -0
- includecpp-3.7.3.dist-info/RECORD +49 -0
- includecpp-3.7.3.dist-info/WHEEL +5 -0
- includecpp-3.7.3.dist-info/entry_points.txt +2 -0
- includecpp-3.7.3.dist-info/licenses/LICENSE +21 -0
- includecpp-3.7.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1903 @@
|
|
|
1
|
+
#include "parser.h"
|
|
2
|
+
#include <iostream>
|
|
3
|
+
#include <fstream>
|
|
4
|
+
#include <sstream>
|
|
5
|
+
#include <regex>
|
|
6
|
+
#include <ctime>
|
|
7
|
+
#include <iomanip>
|
|
8
|
+
#include <functional>
|
|
9
|
+
#include <filesystem>
|
|
10
|
+
#include <iterator>
|
|
11
|
+
#include <set>
|
|
12
|
+
|
|
13
|
+
int API::main(int argc, char* argv[]) {
|
|
14
|
+
if (argc < 5) {
|
|
15
|
+
std::cerr << "Usage: plugin_gen <plugins_dir> <bindings_output> <sources_output> <registry_output>" << std::endl;
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
std::string plugins_dir = argv[1];
|
|
20
|
+
std::string bindings_output = argv[2];
|
|
21
|
+
std::string sources_output = argv[3];
|
|
22
|
+
std::string registry_output = argv[4];
|
|
23
|
+
|
|
24
|
+
std::cout << "Plugin Generator starting..." << std::endl;
|
|
25
|
+
std::cout << "Plugins directory: " << plugins_dir << std::endl;
|
|
26
|
+
std::cout << "Output: " << bindings_output << std::endl;
|
|
27
|
+
|
|
28
|
+
auto modules = parse_all_cp_files(plugins_dir);
|
|
29
|
+
if (modules.empty()) {
|
|
30
|
+
std::cerr << "ERROR: No modules found or parsed successfully in " << plugins_dir << std::endl;
|
|
31
|
+
std::cerr << "Please ensure:" << std::endl;
|
|
32
|
+
std::cerr << " 1. The plugins directory exists" << std::endl;
|
|
33
|
+
std::cerr << " 2. There are .cp files in the directory" << std::endl;
|
|
34
|
+
std::cerr << " 3. The .cp files have valid syntax" << std::endl;
|
|
35
|
+
return 1; // Exit with error if no modules found
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!write_files(modules, bindings_output, sources_output)) {
|
|
39
|
+
std::cerr << "ERROR: Failed to write output files!" << std::endl;
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
std::string registry_json = generate_registry_json(modules, plugins_dir);
|
|
44
|
+
std::ofstream registry_file(registry_output);
|
|
45
|
+
if (!registry_file.is_open()) {
|
|
46
|
+
std::cerr << "ERROR: Cannot open registry file for writing: " << registry_output << std::endl;
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
registry_file << registry_json;
|
|
51
|
+
|
|
52
|
+
if (registry_file.fail() || registry_file.bad()) {
|
|
53
|
+
std::cerr << "ERROR: Failed to write to registry file: " << registry_output << std::endl;
|
|
54
|
+
registry_file.close();
|
|
55
|
+
return 1;
|
|
56
|
+
}
|
|
57
|
+
registry_file.close();
|
|
58
|
+
|
|
59
|
+
std::cout << "SUCCESS: Generated bindings for " << modules.size() << " module(s)" << std::endl;
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
std::vector<ModuleDescriptor> API::parse_all_cp_files(const std::string& plugins_dir) {
|
|
64
|
+
std::vector<ModuleDescriptor> modules;
|
|
65
|
+
|
|
66
|
+
if (!std::filesystem::exists(plugins_dir)) {
|
|
67
|
+
std::cerr << "ERROR: Plugins directory does not exist: " << plugins_dir << std::endl;
|
|
68
|
+
std::cerr << "Current working directory: " << std::filesystem::current_path() << std::endl;
|
|
69
|
+
return modules;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
std::cout << "Scanning for .cp files in: " << plugins_dir << std::endl;
|
|
73
|
+
|
|
74
|
+
int cp_file_count = 0;
|
|
75
|
+
for (const auto& entry : std::filesystem::directory_iterator(plugins_dir)) {
|
|
76
|
+
if (entry.path().extension() == ".cp") {
|
|
77
|
+
cp_file_count++;
|
|
78
|
+
std::cout << "Found .cp file: " << entry.path().filename() << std::endl;
|
|
79
|
+
try {
|
|
80
|
+
modules.push_back(parse_cp_file(entry.path().string()));
|
|
81
|
+
std::cout << " [OK] Successfully parsed: " << entry.path().filename() << std::endl;
|
|
82
|
+
} catch (const std::exception& e) {
|
|
83
|
+
std::cerr << " [ERROR] Parsing " << entry.path().filename() << ": " << e.what() << std::endl;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (cp_file_count == 0) {
|
|
89
|
+
std::cerr << "ERROR: No .cp files found in directory" << std::endl;
|
|
90
|
+
} else {
|
|
91
|
+
std::cout << "Total: " << cp_file_count << " .cp file(s) found, "
|
|
92
|
+
<< modules.size() << " successfully parsed" << std::endl;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return modules;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
void API::validate_module_name(const std::string& name, int line_num) {
|
|
99
|
+
if (name.empty()) {
|
|
100
|
+
throw std::runtime_error("Line " + std::to_string(line_num) +
|
|
101
|
+
": Empty module name");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!std::isalpha(static_cast<unsigned char>(name[0])) && name[0] != '_') {
|
|
105
|
+
throw std::runtime_error("Line " + std::to_string(line_num) +
|
|
106
|
+
": Invalid module name '" + name +
|
|
107
|
+
"' (must start with letter or underscore)");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (char c : name) {
|
|
111
|
+
if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_') {
|
|
112
|
+
throw std::runtime_error("Line " + std::to_string(line_num) +
|
|
113
|
+
": Invalid character '" + std::string(1, c) +
|
|
114
|
+
"' in module name");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
bool API::validate_bindings_code(const std::string& code) {
|
|
120
|
+
int brace_count = 0;
|
|
121
|
+
for (char c : code) {
|
|
122
|
+
if (c == '{') brace_count++;
|
|
123
|
+
if (c == '}') brace_count--;
|
|
124
|
+
if (brace_count < 0) {
|
|
125
|
+
std::cerr << "ERROR: Unbalanced braces (too many closing braces)" << std::endl;
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (brace_count != 0) {
|
|
131
|
+
std::cerr << "ERROR: Unbalanced braces (missing " << brace_count << " closing braces)" << std::endl;
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (code.find("#include <pybind11/pybind11.h>") == std::string::npos) {
|
|
136
|
+
std::cerr << "ERROR: Missing required include: pybind11/pybind11.h" << std::endl;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (code.find("PYBIND11_MODULE(api, m)") == std::string::npos) {
|
|
141
|
+
std::cerr << "ERROR: Missing PYBIND11_MODULE(api, m) definition" << std::endl;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (code.find(";;") != std::string::npos) {
|
|
146
|
+
std::cerr << "WARNING: Double semicolon detected (possible syntax error)" << std::endl;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
void API::validate_namespace_includecpp(const std::string& source_path, const std::string& module_name) {
|
|
153
|
+
// Check if the source file contains "namespace includecpp"
|
|
154
|
+
std::ifstream file(source_path);
|
|
155
|
+
if (!file.is_open()) {
|
|
156
|
+
// File doesn't exist yet or can't be opened - skip validation
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
std::string content((std::istreambuf_iterator<char>(file)),
|
|
161
|
+
std::istreambuf_iterator<char>());
|
|
162
|
+
file.close();
|
|
163
|
+
|
|
164
|
+
// Look for "namespace includecpp" with optional whitespace
|
|
165
|
+
std::regex ns_regex(R"(\bnamespace\s+includecpp\s*\{)");
|
|
166
|
+
if (!std::regex_search(content, ns_regex)) {
|
|
167
|
+
std::string error_msg =
|
|
168
|
+
"\n"
|
|
169
|
+
"+============================================================================+\n"
|
|
170
|
+
"| NAMESPACE ERROR |\n"
|
|
171
|
+
"+============================================================================+\n"
|
|
172
|
+
"\n"
|
|
173
|
+
"Module: " + module_name + "\n"
|
|
174
|
+
"Source: " + source_path + "\n"
|
|
175
|
+
"\n"
|
|
176
|
+
"ERROR: Source file must contain 'namespace includecpp { ... }'\n"
|
|
177
|
+
"\n"
|
|
178
|
+
"All IncludeCPP code must be wrapped in the 'includecpp' namespace.\n"
|
|
179
|
+
"This is required for proper Python binding generation.\n"
|
|
180
|
+
"\n"
|
|
181
|
+
"Example:\n"
|
|
182
|
+
"\n"
|
|
183
|
+
" #include <string>\n"
|
|
184
|
+
" \n"
|
|
185
|
+
" namespace includecpp {\n"
|
|
186
|
+
" \n"
|
|
187
|
+
" int add(int a, int b) {\n"
|
|
188
|
+
" return a + b;\n"
|
|
189
|
+
" }\n"
|
|
190
|
+
" \n"
|
|
191
|
+
" class MyClass {\n"
|
|
192
|
+
" public:\n"
|
|
193
|
+
" void hello() { ... }\n"
|
|
194
|
+
" };\n"
|
|
195
|
+
" \n"
|
|
196
|
+
" } // namespace includecpp\n"
|
|
197
|
+
"\n";
|
|
198
|
+
throw std::runtime_error(error_msg);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
ModuleDescriptor API::parse_cp_file(const std::string& filepath) {
|
|
203
|
+
ModuleDescriptor desc;
|
|
204
|
+
desc.has_header = false;
|
|
205
|
+
desc.expose_all = false;
|
|
206
|
+
|
|
207
|
+
std::ifstream file(filepath);
|
|
208
|
+
if (!file.is_open()) {
|
|
209
|
+
throw std::runtime_error("Cannot open file: " + filepath);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
std::stringstream buffer;
|
|
213
|
+
buffer << file.rdbuf();
|
|
214
|
+
std::string content = buffer.str();
|
|
215
|
+
|
|
216
|
+
auto lines = split(content, '\n');
|
|
217
|
+
|
|
218
|
+
bool in_public_block = false;
|
|
219
|
+
std::string public_content;
|
|
220
|
+
int line_num = 0;
|
|
221
|
+
|
|
222
|
+
for (const auto& line_raw : lines) {
|
|
223
|
+
line_num++;
|
|
224
|
+
std::string line = trim(line_raw);
|
|
225
|
+
if (line.empty() || line[0] == '#') continue;
|
|
226
|
+
|
|
227
|
+
#ifdef VSRAM_DEBUG
|
|
228
|
+
std::cout << "DEBUG: Processing line " << line_num << ": " << line << std::endl;
|
|
229
|
+
#endif
|
|
230
|
+
|
|
231
|
+
if (!in_public_block) {
|
|
232
|
+
if (starts_with(line, "SOURCE")) {
|
|
233
|
+
size_t source_start = line.find('(');
|
|
234
|
+
size_t source_end = line.find(')');
|
|
235
|
+
if (source_start != std::string::npos && source_end != std::string::npos) {
|
|
236
|
+
desc.source_path = trim(line.substr(source_start + 1, source_end - source_start - 1));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
size_t header_pos = line.find("HEADER");
|
|
240
|
+
if (header_pos != std::string::npos) {
|
|
241
|
+
desc.has_header = true;
|
|
242
|
+
size_t header_start = line.find('(', header_pos);
|
|
243
|
+
size_t header_end = line.find(')', header_pos);
|
|
244
|
+
if (header_start != std::string::npos && header_end != std::string::npos) {
|
|
245
|
+
desc.header_path = trim(line.substr(header_start + 1, header_end - header_start - 1));
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
size_t last_space = line.rfind(' ');
|
|
250
|
+
if (last_space != std::string::npos) {
|
|
251
|
+
desc.module_name = trim(line.substr(last_space + 1));
|
|
252
|
+
validate_module_name(desc.module_name, line_num);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// v2.0: Parse SOURCES() for multi-file modules
|
|
256
|
+
else if (starts_with(line, "SOURCES")) {
|
|
257
|
+
size_t sources_start = line.find('(');
|
|
258
|
+
size_t sources_end = line.find(')');
|
|
259
|
+
if (sources_start != std::string::npos && sources_end != std::string::npos) {
|
|
260
|
+
std::string sources_str = line.substr(sources_start + 1, sources_end - sources_start - 1);
|
|
261
|
+
auto source_files = split(sources_str, ',');
|
|
262
|
+
for (auto& src : source_files) {
|
|
263
|
+
desc.additional_sources.push_back(trim(src));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// v2.0: Parse DEPENDS() for module dependencies
|
|
268
|
+
else if (starts_with(line, "DEPENDS")) {
|
|
269
|
+
size_t depends_start = line.find('(');
|
|
270
|
+
size_t depends_end = line.find(')');
|
|
271
|
+
if (depends_start != std::string::npos && depends_end != std::string::npos) {
|
|
272
|
+
std::string depends_str = line.substr(depends_start + 1, depends_end - depends_start - 1);
|
|
273
|
+
auto modules = split(depends_str, ',');
|
|
274
|
+
|
|
275
|
+
for (const auto& mod : modules) {
|
|
276
|
+
std::string trimmed = trim(mod);
|
|
277
|
+
ModuleDependency dep;
|
|
278
|
+
|
|
279
|
+
// Check for type list: module[Type1, Type2]
|
|
280
|
+
size_t bracket_start = trimmed.find('[');
|
|
281
|
+
if (bracket_start != std::string::npos) {
|
|
282
|
+
dep.target_module = trimmed.substr(0, bracket_start);
|
|
283
|
+
size_t bracket_end = trimmed.find(']');
|
|
284
|
+
if (bracket_end != std::string::npos) {
|
|
285
|
+
std::string types_str = trimmed.substr(bracket_start + 1, bracket_end - bracket_start - 1);
|
|
286
|
+
auto types = split(types_str, ',');
|
|
287
|
+
for (auto& t : types) {
|
|
288
|
+
dep.required_types.push_back(trim(t));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
dep.target_module = trimmed;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
desc.dependencies.push_back(dep);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else if (starts_with(line, "PUBLIC")) {
|
|
300
|
+
in_public_block = true;
|
|
301
|
+
size_t paren = line.find('(');
|
|
302
|
+
if (paren != std::string::npos) {
|
|
303
|
+
public_content += line.substr(paren + 1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
public_content += "\n" + line;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
size_t last_paren = public_content.rfind(')');
|
|
313
|
+
if (last_paren != std::string::npos) {
|
|
314
|
+
public_content = public_content.substr(0, last_paren);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public_content = trim(public_content);
|
|
318
|
+
|
|
319
|
+
#ifdef VSRAM_DEBUG
|
|
320
|
+
std::cout << "DEBUG: public_content = [" << public_content << "]" << std::endl;
|
|
321
|
+
#endif
|
|
322
|
+
|
|
323
|
+
if (trim(public_content) == "ALL") {
|
|
324
|
+
desc.expose_all = true;
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
auto public_lines = split(public_content, '\n');
|
|
328
|
+
#ifdef VSRAM_DEBUG
|
|
329
|
+
std::cout << "DEBUG: Found " << public_lines.size() << " lines in PUBLIC block" << std::endl;
|
|
330
|
+
#endif
|
|
331
|
+
|
|
332
|
+
for (size_t i = 0; i < public_lines.size(); ++i) {
|
|
333
|
+
std::string cleaned = trim(public_lines[i]);
|
|
334
|
+
if (cleaned.empty()) continue;
|
|
335
|
+
|
|
336
|
+
size_t and_pos = cleaned.find("&&");
|
|
337
|
+
if (and_pos != std::string::npos) {
|
|
338
|
+
cleaned = trim(cleaned.substr(0, and_pos));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (cleaned.find("TEMPLATE_FUNC") != std::string::npos) {
|
|
342
|
+
FunctionBinding fb;
|
|
343
|
+
fb.is_template = true;
|
|
344
|
+
auto parts = split(cleaned, ' ');
|
|
345
|
+
if (parts.size() >= 2) {
|
|
346
|
+
fb.module_name = parts[0];
|
|
347
|
+
|
|
348
|
+
size_t tfunc_pos = cleaned.find("TEMPLATE_FUNC(");
|
|
349
|
+
if (tfunc_pos != std::string::npos) {
|
|
350
|
+
size_t func_start = tfunc_pos + 13;
|
|
351
|
+
size_t func_end = cleaned.find(')', func_start);
|
|
352
|
+
fb.function_name = safe_extract_between(cleaned, func_start, func_end, "TEMPLATE_FUNC");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
size_t types_pos = cleaned.find("TYPES(");
|
|
356
|
+
if (types_pos != std::string::npos) {
|
|
357
|
+
size_t types_start = types_pos + 6;
|
|
358
|
+
size_t types_end = cleaned.find(')', types_start);
|
|
359
|
+
if (types_end != std::string::npos) {
|
|
360
|
+
std::string types_str = cleaned.substr(types_start, types_end - types_start);
|
|
361
|
+
auto types = split(types_str, ',');
|
|
362
|
+
for (auto& t : types) {
|
|
363
|
+
fb.template_types.push_back(trim(t));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
desc.functions.push_back(fb);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else if (cleaned.find("FUNC") != std::string::npos) {
|
|
372
|
+
FunctionBinding fb;
|
|
373
|
+
auto parts = split(cleaned, ' ');
|
|
374
|
+
if (parts.size() >= 2) {
|
|
375
|
+
fb.module_name = parts[0];
|
|
376
|
+
|
|
377
|
+
size_t func_start = cleaned.find('(');
|
|
378
|
+
size_t func_end = cleaned.find(')');
|
|
379
|
+
fb.function_name = safe_extract_between(cleaned, func_start, func_end, "FUNC");
|
|
380
|
+
desc.functions.push_back(fb);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
else if (cleaned.find("CLASS") != std::string::npos) {
|
|
384
|
+
ClassBinding cb;
|
|
385
|
+
cb.auto_bind_all = false;
|
|
386
|
+
auto parts = split(cleaned, ' ');
|
|
387
|
+
if (parts.size() >= 2) {
|
|
388
|
+
cb.module_name = parts[0];
|
|
389
|
+
|
|
390
|
+
size_t class_start = cleaned.find('(');
|
|
391
|
+
size_t class_end = cleaned.find(')');
|
|
392
|
+
cb.class_name = safe_extract_between(cleaned, class_start, class_end, "CLASS");
|
|
393
|
+
|
|
394
|
+
// Check for METHOD/FIELD block: CLASS(Name) { ... }
|
|
395
|
+
size_t brace_open = cleaned.find('{');
|
|
396
|
+
if (brace_open != std::string::npos) {
|
|
397
|
+
// Collect all lines until closing brace
|
|
398
|
+
std::string method_block;
|
|
399
|
+
bool found_close = false;
|
|
400
|
+
|
|
401
|
+
// Check if closing brace on same line
|
|
402
|
+
size_t brace_close = cleaned.find('}', brace_open);
|
|
403
|
+
if (brace_close != std::string::npos) {
|
|
404
|
+
method_block = cleaned.substr(brace_open + 1, brace_close - brace_open - 1);
|
|
405
|
+
found_close = true;
|
|
406
|
+
} else {
|
|
407
|
+
// Collect from next lines
|
|
408
|
+
for (size_t j = i + 1; j < public_lines.size(); ++j) {
|
|
409
|
+
std::string next_line = trim(public_lines[j]);
|
|
410
|
+
size_t close_pos = next_line.find('}');
|
|
411
|
+
|
|
412
|
+
if (close_pos != std::string::npos) {
|
|
413
|
+
method_block += next_line.substr(0, close_pos);
|
|
414
|
+
i = j; // Skip these lines in main loop
|
|
415
|
+
found_close = true;
|
|
416
|
+
break;
|
|
417
|
+
} else {
|
|
418
|
+
method_block += next_line + "\n";
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Parse METHOD(...) and FIELD(...) entries
|
|
424
|
+
auto method_lines = split(method_block, '\n');
|
|
425
|
+
for (const auto& mline : method_lines) {
|
|
426
|
+
std::string mtrim = trim(mline);
|
|
427
|
+
if (mtrim.empty()) continue;
|
|
428
|
+
|
|
429
|
+
// v2.4.3: Parse CONSTRUCTOR(type1, type2, ...) for parametrized constructors
|
|
430
|
+
if (mtrim.find("CONSTRUCTOR") != std::string::npos) {
|
|
431
|
+
size_t c_start = mtrim.find('(');
|
|
432
|
+
size_t c_end = mtrim.rfind(')');
|
|
433
|
+
if (c_start != std::string::npos && c_end != std::string::npos) {
|
|
434
|
+
std::string params_str = mtrim.substr(c_start + 1, c_end - c_start - 1);
|
|
435
|
+
ConstructorInfo ctor;
|
|
436
|
+
if (!params_str.empty()) {
|
|
437
|
+
auto params = split(params_str, ',');
|
|
438
|
+
for (auto& p : params) {
|
|
439
|
+
std::string param_type = trim(p);
|
|
440
|
+
if (!param_type.empty()) {
|
|
441
|
+
ctor.param_types.push_back(param_type);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
cb.constructors.push_back(ctor);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// v2.4.13: Extended METHOD parsing with overload support
|
|
449
|
+
// Supports: METHOD(name), METHOD(name, type1, type2), METHOD_CONST(name, type1)
|
|
450
|
+
else if (mtrim.find("METHOD") != std::string::npos) {
|
|
451
|
+
MethodSignature sig;
|
|
452
|
+
bool is_const_method = mtrim.find("METHOD_CONST") != std::string::npos;
|
|
453
|
+
|
|
454
|
+
size_t m_start = mtrim.find('(');
|
|
455
|
+
size_t m_end = mtrim.rfind(')'); // Use rfind to handle nested templates
|
|
456
|
+
|
|
457
|
+
if (m_start != std::string::npos && m_end != std::string::npos) {
|
|
458
|
+
std::string content = mtrim.substr(m_start + 1, m_end - m_start - 1);
|
|
459
|
+
|
|
460
|
+
// Parse method name and optional parameter types
|
|
461
|
+
// Format: "methodName" or "methodName, const Circle&, const Rect&"
|
|
462
|
+
std::vector<std::string> parts;
|
|
463
|
+
int template_depth = 0;
|
|
464
|
+
std::string current_part;
|
|
465
|
+
|
|
466
|
+
for (char c : content) {
|
|
467
|
+
if (c == '<') {
|
|
468
|
+
template_depth++;
|
|
469
|
+
current_part += c;
|
|
470
|
+
} else if (c == '>') {
|
|
471
|
+
template_depth--;
|
|
472
|
+
current_part += c;
|
|
473
|
+
} else if (c == ',' && template_depth == 0) {
|
|
474
|
+
parts.push_back(trim(current_part));
|
|
475
|
+
current_part.clear();
|
|
476
|
+
} else {
|
|
477
|
+
current_part += c;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (!current_part.empty()) {
|
|
481
|
+
parts.push_back(trim(current_part));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (!parts.empty()) {
|
|
485
|
+
sig.name = parts[0];
|
|
486
|
+
sig.is_const = is_const_method;
|
|
487
|
+
|
|
488
|
+
// Remaining parts are parameter types for overload resolution
|
|
489
|
+
for (size_t i = 1; i < parts.size(); ++i) {
|
|
490
|
+
sig.param_types.push_back(parts[i]);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// v3.3.22: Also populate parameters for display in get command
|
|
494
|
+
for (size_t i = 0; i < sig.param_types.size(); ++i) {
|
|
495
|
+
ParameterInfo param;
|
|
496
|
+
std::string type_str = sig.param_types[i];
|
|
497
|
+
param.type = type_str;
|
|
498
|
+
param.name = "arg" + std::to_string(i + 1);
|
|
499
|
+
// Check for const
|
|
500
|
+
if (type_str.find("const ") != std::string::npos) {
|
|
501
|
+
param.is_const = true;
|
|
502
|
+
}
|
|
503
|
+
// Check for reference
|
|
504
|
+
if (!type_str.empty() && type_str.back() == '&') {
|
|
505
|
+
param.is_reference = true;
|
|
506
|
+
}
|
|
507
|
+
// Check for pointer
|
|
508
|
+
if (!type_str.empty() && type_str.back() == '*') {
|
|
509
|
+
param.is_pointer = true;
|
|
510
|
+
}
|
|
511
|
+
sig.parameters.push_back(param);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
cb.method_signatures.push_back(sig);
|
|
515
|
+
// Also add to legacy methods list for backward compatibility
|
|
516
|
+
cb.methods.push_back(sig.name);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else if (mtrim.find("FIELD") != std::string::npos) {
|
|
521
|
+
size_t f_start = mtrim.find('(');
|
|
522
|
+
size_t f_end = mtrim.find(')');
|
|
523
|
+
if (f_start != std::string::npos && f_end != std::string::npos) {
|
|
524
|
+
std::string field_name = safe_extract_between(mtrim, f_start, f_end, "FIELD");
|
|
525
|
+
cb.fields.push_back(field_name);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
desc.classes.push_back(cb);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else if (cleaned.find("VAR") != std::string::npos) {
|
|
535
|
+
VariableBinding vb;
|
|
536
|
+
auto parts = split(cleaned, ' ');
|
|
537
|
+
if (parts.size() >= 2) {
|
|
538
|
+
vb.module_name = parts[0];
|
|
539
|
+
|
|
540
|
+
size_t var_start = cleaned.find('(');
|
|
541
|
+
size_t var_end = cleaned.find(')');
|
|
542
|
+
vb.variable_name = safe_extract_between(cleaned, var_start, var_end, "VAR");
|
|
543
|
+
desc.variables.push_back(vb);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// v2.0: Parse STRUCT() for Plain-Old-Data types
|
|
547
|
+
else if (cleaned.find("STRUCT") != std::string::npos) {
|
|
548
|
+
StructBinding sb;
|
|
549
|
+
auto parts = split(cleaned, ' ');
|
|
550
|
+
if (parts.size() >= 2) {
|
|
551
|
+
sb.module_name = parts[0];
|
|
552
|
+
|
|
553
|
+
size_t struct_start = cleaned.find("STRUCT(");
|
|
554
|
+
size_t struct_end = cleaned.find(')', struct_start);
|
|
555
|
+
// safe_extract_between adds +1 to start_pos, so pass position of '(' not after it
|
|
556
|
+
size_t struct_paren = struct_start + 6; // Position of '(' in "STRUCT("
|
|
557
|
+
sb.struct_name = safe_extract_between(cleaned, struct_paren, struct_end, "STRUCT");
|
|
558
|
+
|
|
559
|
+
// Check for TYPES() clause for template structs
|
|
560
|
+
size_t types_pos = cleaned.find("TYPES(");
|
|
561
|
+
if (types_pos != std::string::npos) {
|
|
562
|
+
sb.is_template = true;
|
|
563
|
+
size_t types_start = types_pos + 6;
|
|
564
|
+
size_t types_end = cleaned.find(')', types_start);
|
|
565
|
+
if (types_end != std::string::npos) {
|
|
566
|
+
std::string types_str = cleaned.substr(types_start, types_end - types_start);
|
|
567
|
+
auto types = split(types_str, ',');
|
|
568
|
+
for (auto& t : types) {
|
|
569
|
+
sb.template_types.push_back(trim(t));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Check for FIELD block: STRUCT(Name) { FIELD(type, name) ... }
|
|
575
|
+
size_t brace_open = cleaned.find('{');
|
|
576
|
+
if (brace_open != std::string::npos) {
|
|
577
|
+
std::string field_block;
|
|
578
|
+
bool found_close = false;
|
|
579
|
+
|
|
580
|
+
// Check if closing brace on same line
|
|
581
|
+
size_t brace_close = cleaned.find('}', brace_open);
|
|
582
|
+
if (brace_close != std::string::npos) {
|
|
583
|
+
field_block = cleaned.substr(brace_open + 1, brace_close - brace_open - 1);
|
|
584
|
+
found_close = true;
|
|
585
|
+
} else {
|
|
586
|
+
// Collect from next lines
|
|
587
|
+
for (size_t j = i + 1; j < public_lines.size(); ++j) {
|
|
588
|
+
std::string next_line = trim(public_lines[j]);
|
|
589
|
+
size_t close_pos = next_line.find('}');
|
|
590
|
+
|
|
591
|
+
if (close_pos != std::string::npos) {
|
|
592
|
+
field_block += next_line.substr(0, close_pos);
|
|
593
|
+
i = j; // Skip these lines in main loop
|
|
594
|
+
found_close = true;
|
|
595
|
+
break;
|
|
596
|
+
} else {
|
|
597
|
+
field_block += next_line + "\n";
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Parse FIELD(type, name) entries
|
|
603
|
+
auto field_lines = split(field_block, '\n');
|
|
604
|
+
for (const auto& fline : field_lines) {
|
|
605
|
+
std::string ftrim = trim(fline);
|
|
606
|
+
if (ftrim.empty()) continue;
|
|
607
|
+
|
|
608
|
+
if (ftrim.find("FIELD(") != std::string::npos) {
|
|
609
|
+
size_t f_start = ftrim.find('(');
|
|
610
|
+
size_t f_end = ftrim.find(')');
|
|
611
|
+
if (f_start != std::string::npos && f_end != std::string::npos) {
|
|
612
|
+
std::string field_content = ftrim.substr(f_start + 1, f_end - f_start - 1);
|
|
613
|
+
auto field_parts = split(field_content, ',');
|
|
614
|
+
|
|
615
|
+
if (field_parts.size() >= 2) {
|
|
616
|
+
std::string field_type = trim(field_parts[0]);
|
|
617
|
+
std::string field_name = trim(field_parts[1]);
|
|
618
|
+
sb.fields.push_back({field_type, field_name});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
desc.structs.push_back(sb);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Parse DOC() statements from entire file content
|
|
632
|
+
auto doc_map = parse_doc_statements(content);
|
|
633
|
+
|
|
634
|
+
// Apply documentation to functions
|
|
635
|
+
for (auto& func : desc.functions) {
|
|
636
|
+
std::string key = "FUNC(" + func.function_name + ")";
|
|
637
|
+
if (doc_map.count(key)) {
|
|
638
|
+
func.documentation = doc_map[key];
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Apply documentation to classes and methods
|
|
643
|
+
for (auto& cls : desc.classes) {
|
|
644
|
+
std::string class_key = "CLASS(" + cls.class_name + ")";
|
|
645
|
+
if (doc_map.count(class_key)) {
|
|
646
|
+
cls.documentation = doc_map[class_key];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Apply method documentation
|
|
650
|
+
for (const auto& method : cls.methods) {
|
|
651
|
+
std::string method_key = "METHOD(" + method + ")";
|
|
652
|
+
if (doc_map.count(method_key)) {
|
|
653
|
+
cls.method_docs[method] = doc_map[method_key];
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Apply documentation to variables
|
|
659
|
+
for (auto& var : desc.variables) {
|
|
660
|
+
std::string key = "VAR(" + var.variable_name + ")";
|
|
661
|
+
if (doc_map.count(key)) {
|
|
662
|
+
var.documentation = doc_map[key];
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// v2.0: Apply documentation to structs
|
|
667
|
+
for (auto& st : desc.structs) {
|
|
668
|
+
std::string key = "STRUCT(" + st.struct_name + ")";
|
|
669
|
+
if (doc_map.count(key)) {
|
|
670
|
+
st.documentation = doc_map[key];
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// v2.3.5: Extract function signatures from C++ source file for IntelliSense
|
|
675
|
+
if (!desc.source_path.empty() && std::filesystem::exists(desc.source_path)) {
|
|
676
|
+
auto cpp_signatures = parse_cpp_function_signatures(desc.source_path);
|
|
677
|
+
|
|
678
|
+
// Match extracted signatures with declared functions
|
|
679
|
+
for (auto& func : desc.functions) {
|
|
680
|
+
for (const auto& sig : cpp_signatures) {
|
|
681
|
+
if (sig.function_name == func.function_name) {
|
|
682
|
+
func.return_type = sig.return_type;
|
|
683
|
+
func.parameters = sig.parameters;
|
|
684
|
+
func.is_const = sig.is_const;
|
|
685
|
+
func.is_static = sig.is_static;
|
|
686
|
+
func.is_inline = sig.is_inline;
|
|
687
|
+
func.full_signature = sig.full_signature;
|
|
688
|
+
break;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// TODO v2.3.5: Extract class method signatures (future enhancement)
|
|
694
|
+
// This would require more complex parsing to associate methods with classes
|
|
695
|
+
// For now, classes use basic method names without signatures
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// v2.4.1: Validate that source file uses namespace includecpp
|
|
699
|
+
if (!desc.source_path.empty()) {
|
|
700
|
+
validate_namespace_includecpp(desc.source_path, desc.module_name);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// v3.4.1: Auto-detect header from #include directives in source file
|
|
704
|
+
if (!desc.has_header && !desc.source_path.empty()) {
|
|
705
|
+
std::ifstream source_file(desc.source_path);
|
|
706
|
+
if (source_file.is_open()) {
|
|
707
|
+
std::string line;
|
|
708
|
+
std::regex include_regex(R"re(^\s*#include\s*"([^"]+\.h(?:pp)?)")re");
|
|
709
|
+
std::smatch match;
|
|
710
|
+
|
|
711
|
+
while (std::getline(source_file, line)) {
|
|
712
|
+
if (std::regex_search(line, match, include_regex)) {
|
|
713
|
+
std::string header_name = match[1].str();
|
|
714
|
+
// Skip standard headers and pybind11 headers
|
|
715
|
+
if (header_name.find("pybind11") == std::string::npos &&
|
|
716
|
+
header_name.find("std") == std::string::npos) {
|
|
717
|
+
// Found a local header - construct the path
|
|
718
|
+
std::filesystem::path source_path(desc.source_path);
|
|
719
|
+
std::filesystem::path header_path = source_path.parent_path() / header_name;
|
|
720
|
+
|
|
721
|
+
// Check if the header file exists
|
|
722
|
+
if (std::filesystem::exists(header_path)) {
|
|
723
|
+
desc.header_path = header_path.string();
|
|
724
|
+
desc.has_header = true;
|
|
725
|
+
std::cout << "NOTE: Auto-detected header for '" << desc.module_name
|
|
726
|
+
<< "': " << desc.header_path << std::endl;
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
// Also try in include/ subdirectory
|
|
730
|
+
header_path = source_path.parent_path().parent_path() / "include" / header_name;
|
|
731
|
+
if (std::filesystem::exists(header_path)) {
|
|
732
|
+
desc.header_path = header_path.string();
|
|
733
|
+
desc.has_header = true;
|
|
734
|
+
std::cout << "NOTE: Auto-detected header for '" << desc.module_name
|
|
735
|
+
<< "': " << desc.header_path << std::endl;
|
|
736
|
+
break;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return desc;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
std::string API::generate_class_bindings(const ClassBinding& cls, const ModuleDescriptor& mod) {
|
|
748
|
+
std::ostringstream code;
|
|
749
|
+
|
|
750
|
+
code << " py::class_<" << cls.class_name << ">("
|
|
751
|
+
<< mod.module_name << "_module, \"" << cls.class_name << "\")\n";
|
|
752
|
+
|
|
753
|
+
// v2.4.3: Generate all constructor overloads from CONSTRUCTOR() entries
|
|
754
|
+
if (cls.constructors.empty()) {
|
|
755
|
+
// Backward compatibility: default constructor if none specified
|
|
756
|
+
code << " .def(py::init<>())";
|
|
757
|
+
} else {
|
|
758
|
+
bool first = true;
|
|
759
|
+
for (const auto& ctor : cls.constructors) {
|
|
760
|
+
if (!first) code << "\n";
|
|
761
|
+
first = false;
|
|
762
|
+
|
|
763
|
+
code << " .def(py::init<";
|
|
764
|
+
for (size_t i = 0; i < ctor.param_types.size(); ++i) {
|
|
765
|
+
if (i > 0) code << ", ";
|
|
766
|
+
code << ctor.param_types[i];
|
|
767
|
+
}
|
|
768
|
+
code << ">())";
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Bind Initialize static method only if there's a default constructor
|
|
773
|
+
bool has_default_ctor = cls.constructors.empty(); // backward compatibility
|
|
774
|
+
if (!has_default_ctor) {
|
|
775
|
+
for (const auto& ctor : cls.constructors) {
|
|
776
|
+
if (ctor.param_types.empty()) {
|
|
777
|
+
has_default_ctor = true;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (has_default_ctor) {
|
|
783
|
+
code << "\n .def_static(\"Initialize\", []() { return " << cls.class_name << "(); })";
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// v2.4.13: Group methods by name to detect overloads
|
|
787
|
+
std::map<std::string, std::vector<MethodSignature>> method_groups;
|
|
788
|
+
for (const auto& sig : cls.method_signatures) {
|
|
789
|
+
method_groups[sig.name].push_back(sig);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// v2.4.13: Handle methods with overload detection
|
|
793
|
+
if (!cls.method_signatures.empty()) {
|
|
794
|
+
// Use new signature-based bindings with overload support
|
|
795
|
+
for (const auto& [method_name, signatures] : method_groups) {
|
|
796
|
+
if (signatures.size() == 1 && signatures[0].param_types.empty()) {
|
|
797
|
+
// Single method without explicit signature - use simple binding
|
|
798
|
+
const auto& sig = signatures[0];
|
|
799
|
+
code << "\n .def(\"" << method_name << "\", &"
|
|
800
|
+
<< cls.class_name << "::" << method_name << ")";
|
|
801
|
+
} else if (signatures.size() == 1) {
|
|
802
|
+
// Single method with explicit signature - still use overload_cast for safety
|
|
803
|
+
const auto& sig = signatures[0];
|
|
804
|
+
code << "\n .def(\"" << method_name << "\", "
|
|
805
|
+
<< "py::overload_cast<";
|
|
806
|
+
|
|
807
|
+
for (size_t i = 0; i < sig.param_types.size(); ++i) {
|
|
808
|
+
if (i > 0) code << ", ";
|
|
809
|
+
code << sig.param_types[i];
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
code << ">(&" << cls.class_name << "::" << method_name;
|
|
813
|
+
|
|
814
|
+
if (sig.is_const) {
|
|
815
|
+
code << ", py::const_";
|
|
816
|
+
}
|
|
817
|
+
code << "))";
|
|
818
|
+
} else {
|
|
819
|
+
// Multiple overloads - generate py::overload_cast for each
|
|
820
|
+
for (const auto& sig : signatures) {
|
|
821
|
+
code << "\n .def(\"" << method_name << "\", "
|
|
822
|
+
<< "py::overload_cast<";
|
|
823
|
+
|
|
824
|
+
for (size_t i = 0; i < sig.param_types.size(); ++i) {
|
|
825
|
+
if (i > 0) code << ", ";
|
|
826
|
+
code << sig.param_types[i];
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
code << ">(&" << cls.class_name << "::" << method_name;
|
|
830
|
+
|
|
831
|
+
if (sig.is_const) {
|
|
832
|
+
code << ", py::const_";
|
|
833
|
+
}
|
|
834
|
+
code << "))";
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
// Fallback: Legacy method binding (no signature info)
|
|
840
|
+
for (const auto& method : cls.methods) {
|
|
841
|
+
code << "\n .def(\"" << method << "\", &"
|
|
842
|
+
<< cls.class_name << "::" << method << ")";
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Bind fields (read-write access)
|
|
847
|
+
for (const auto& field : cls.fields) {
|
|
848
|
+
code << "\n .def_readwrite(\"" << field
|
|
849
|
+
<< "\", &" << cls.class_name << "::" << field << ")";
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// v2.8: Add __repr__ for better debugging output
|
|
853
|
+
code << "\n .def(\"__repr__\", [](" << cls.class_name << "& self) {\n";
|
|
854
|
+
code << " return \"<" << cls.class_name << " object>\";\n";
|
|
855
|
+
code << " })";
|
|
856
|
+
|
|
857
|
+
code << ";\n\n";
|
|
858
|
+
return code.str();
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// v2.0: Generate bindings for STRUCT (Plain-Old-Data types)
|
|
862
|
+
std::string generate_struct_bindings(const StructBinding& sb, const ModuleDescriptor& mod) {
|
|
863
|
+
std::ostringstream code;
|
|
864
|
+
|
|
865
|
+
if (sb.is_template) {
|
|
866
|
+
// Generate bindings for each template type
|
|
867
|
+
for (const auto& ttype : sb.template_types) {
|
|
868
|
+
std::string struct_full_name = sb.struct_name + "_" + ttype;
|
|
869
|
+
std::string cpp_type = sb.struct_name + "<" + ttype + ">";
|
|
870
|
+
|
|
871
|
+
code << " py::class_<" << cpp_type << ">(";
|
|
872
|
+
code << mod.module_name << "_module, \"" << struct_full_name << "\")\n";
|
|
873
|
+
code << " .def(py::init<>())\n";
|
|
874
|
+
|
|
875
|
+
// Fields - readwrite access
|
|
876
|
+
for (const auto& [field_type, field_name] : sb.fields) {
|
|
877
|
+
std::string actual_type = field_type;
|
|
878
|
+
// Replace template parameter T with actual type
|
|
879
|
+
if (actual_type == "T") {
|
|
880
|
+
actual_type = ttype;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
code << " .def_readwrite(\"" << field_name << "\", &"
|
|
884
|
+
<< cpp_type << "::" << field_name << ")\n";
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Auto-generate to_dict() method
|
|
888
|
+
code << " .def(\"to_dict\", [](" << cpp_type << "& self) {\n";
|
|
889
|
+
code << " py::dict d;\n";
|
|
890
|
+
for (const auto& [field_type, field_name] : sb.fields) {
|
|
891
|
+
code << " d[\"" << field_name << "\"] = self." << field_name << ";\n";
|
|
892
|
+
}
|
|
893
|
+
code << " return d;\n";
|
|
894
|
+
code << " })\n";
|
|
895
|
+
|
|
896
|
+
// Auto-generate from_dict() static method
|
|
897
|
+
code << " .def_static(\"from_dict\", [](py::dict d) {\n";
|
|
898
|
+
code << " " << cpp_type << " obj;\n";
|
|
899
|
+
for (const auto& [field_type, field_name] : sb.fields) {
|
|
900
|
+
std::string actual_type = field_type;
|
|
901
|
+
if (actual_type == "T") {
|
|
902
|
+
actual_type = ttype;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
code << " obj." << field_name << " = d[\"" << field_name
|
|
906
|
+
<< "\"].cast<" << actual_type << ">();\n";
|
|
907
|
+
}
|
|
908
|
+
code << " return obj;\n";
|
|
909
|
+
code << " })\n";
|
|
910
|
+
|
|
911
|
+
// v2.8: Add __repr__ for better debugging output
|
|
912
|
+
code << " .def(\"__repr__\", [](" << cpp_type << "& self) {\n";
|
|
913
|
+
code << " return \"<" << struct_full_name << " object>\";\n";
|
|
914
|
+
code << " });\n\n";
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
// Non-template struct
|
|
918
|
+
code << " py::class_<" << sb.struct_name << ">(";
|
|
919
|
+
code << mod.module_name << "_module, \"" << sb.struct_name << "\")\n";
|
|
920
|
+
code << " .def(py::init<>())\n";
|
|
921
|
+
|
|
922
|
+
// Fields
|
|
923
|
+
for (const auto& [field_type, field_name] : sb.fields) {
|
|
924
|
+
code << " .def_readwrite(\"" << field_name << "\", &"
|
|
925
|
+
<< sb.struct_name << "::" << field_name << ")\n";
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// Auto-generate to_dict() method
|
|
929
|
+
code << " .def(\"to_dict\", [](" << sb.struct_name << "& self) {\n";
|
|
930
|
+
code << " py::dict d;\n";
|
|
931
|
+
for (const auto& [field_type, field_name] : sb.fields) {
|
|
932
|
+
code << " d[\"" << field_name << "\"] = self." << field_name << ";\n";
|
|
933
|
+
}
|
|
934
|
+
code << " return d;\n";
|
|
935
|
+
code << " })\n";
|
|
936
|
+
|
|
937
|
+
// Auto-generate from_dict() static method
|
|
938
|
+
code << " .def_static(\"from_dict\", [](py::dict d) {\n";
|
|
939
|
+
code << " " << sb.struct_name << " obj;\n";
|
|
940
|
+
for (const auto& [field_type, field_name] : sb.fields) {
|
|
941
|
+
code << " obj." << field_name << " = d[\"" << field_name
|
|
942
|
+
<< "\"].cast<" << field_type << ">();\n";
|
|
943
|
+
}
|
|
944
|
+
code << " return obj;\n";
|
|
945
|
+
code << " })\n";
|
|
946
|
+
|
|
947
|
+
// v2.8: Add __repr__ for better debugging output
|
|
948
|
+
code << " .def(\"__repr__\", [](" << sb.struct_name << "& self) {\n";
|
|
949
|
+
code << " return \"<" << sb.struct_name << " object>\";\n";
|
|
950
|
+
code << " });\n\n";
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
return code.str();
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
std::string API::generate_pybind11_code(const std::vector<ModuleDescriptor>& modules) {
|
|
957
|
+
std::ostringstream code;
|
|
958
|
+
|
|
959
|
+
code << "#include <pybind11/pybind11.h>\n";
|
|
960
|
+
code << "#include <pybind11/stl.h>\n";
|
|
961
|
+
code << "#include <pybind11/stl_bind.h>\n";
|
|
962
|
+
code << "#include <pybind11/operators.h>\n";
|
|
963
|
+
code << "#include <pybind11/functional.h>\n";
|
|
964
|
+
code << "#include <pybind11/complex.h>\n";
|
|
965
|
+
code << "#include <pybind11/chrono.h>\n\n";
|
|
966
|
+
|
|
967
|
+
// v3.3.22: Track included headers to prevent duplicates
|
|
968
|
+
std::set<std::string> included_headers;
|
|
969
|
+
std::vector<std::string> ordered_includes;
|
|
970
|
+
|
|
971
|
+
// First pass: collect all headers and detect dependencies
|
|
972
|
+
for (const auto& mod : modules) {
|
|
973
|
+
std::string include_path;
|
|
974
|
+
if (mod.has_header) {
|
|
975
|
+
include_path = replace_all(mod.header_path, "\\", "/");
|
|
976
|
+
} else {
|
|
977
|
+
// Fallback: Include source file directly (header-only in .cpp)
|
|
978
|
+
include_path = replace_all(mod.source_path, "\\", "/");
|
|
979
|
+
std::cout << "NOTE: Module '" << mod.module_name
|
|
980
|
+
<< "' has no separate header, including source file: "
|
|
981
|
+
<< include_path << std::endl;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Skip if already included
|
|
985
|
+
if (included_headers.find(include_path) != included_headers.end()) {
|
|
986
|
+
std::cout << "NOTE: Skipping duplicate include: " << include_path << std::endl;
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
included_headers.insert(include_path);
|
|
991
|
+
ordered_includes.push_back(include_path);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
// Generate includes (duplicates already filtered)
|
|
995
|
+
for (const auto& include_path : ordered_includes) {
|
|
996
|
+
code << "#include \"" << include_path << "\"\n";
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
code << "\nusing namespace includecpp;\n";
|
|
1000
|
+
code << "namespace py = pybind11;\n\n";
|
|
1001
|
+
code << "PYBIND11_MODULE(api, m) {\n";
|
|
1002
|
+
code << " m.doc() = \"Auto-generated C++ API bindings\";\n\n";
|
|
1003
|
+
|
|
1004
|
+
for (const auto& mod : modules) {
|
|
1005
|
+
code << " py::module_ " << mod.module_name << "_module = ";
|
|
1006
|
+
code << "m.def_submodule(\"" << mod.module_name << "\", \"";
|
|
1007
|
+
code << mod.module_name << " module\");\n\n";
|
|
1008
|
+
|
|
1009
|
+
for (const auto& cls : mod.classes) {
|
|
1010
|
+
code << generate_class_bindings(cls, mod);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// v2.0: Generate struct bindings
|
|
1014
|
+
for (const auto& st : mod.structs) {
|
|
1015
|
+
code << generate_struct_bindings(st, mod);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
for (const auto& func : mod.functions) {
|
|
1019
|
+
if (func.is_template && !func.template_types.empty()) {
|
|
1020
|
+
for (const auto& ttype : func.template_types) {
|
|
1021
|
+
code << " " << mod.module_name << "_module.def(\"";
|
|
1022
|
+
code << func.function_name << "_" << ttype << "\", &"
|
|
1023
|
+
<< func.function_name << "<" << ttype << ">);\n";
|
|
1024
|
+
}
|
|
1025
|
+
} else {
|
|
1026
|
+
code << " " << mod.module_name << "_module.def(\"";
|
|
1027
|
+
code << func.function_name << "\", &" << func.function_name << ");\n";
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
for (const auto& var : mod.variables) {
|
|
1032
|
+
code << " " << mod.module_name << "_module.attr(\"";
|
|
1033
|
+
code << var.variable_name << "\") = " << var.variable_name << ";\n";
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
code << "\n";
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
code << "}\n";
|
|
1040
|
+
|
|
1041
|
+
return code.str();
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
bool API::write_files(const std::vector<ModuleDescriptor>& modules,
|
|
1045
|
+
const std::string& bindings_path,
|
|
1046
|
+
const std::string& sources_path) {
|
|
1047
|
+
|
|
1048
|
+
std::string bindings_code = generate_pybind11_code(modules);
|
|
1049
|
+
|
|
1050
|
+
// Validate generated code
|
|
1051
|
+
if (!validate_bindings_code(bindings_code)) {
|
|
1052
|
+
std::cerr << "ERROR: Generated bindings code validation failed!" << std::endl;
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
std::ofstream bindings_file(bindings_path);
|
|
1057
|
+
if (!bindings_file.is_open()) {
|
|
1058
|
+
std::cerr << "ERROR: Cannot open file for writing: " << bindings_path << std::endl;
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
bindings_file << bindings_code;
|
|
1063
|
+
|
|
1064
|
+
if (bindings_file.fail() || bindings_file.bad()) {
|
|
1065
|
+
std::cerr << "ERROR: Failed to write to file: " << bindings_path << std::endl;
|
|
1066
|
+
bindings_file.close();
|
|
1067
|
+
return false;
|
|
1068
|
+
}
|
|
1069
|
+
bindings_file.close();
|
|
1070
|
+
|
|
1071
|
+
std::ofstream sources_file(sources_path);
|
|
1072
|
+
if (!sources_file.is_open()) {
|
|
1073
|
+
std::cerr << "ERROR: Cannot open file for writing: " << sources_path << std::endl;
|
|
1074
|
+
return false;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
for (const auto& mod : modules) {
|
|
1078
|
+
// Only add to sources if module has a separate header
|
|
1079
|
+
// If no header, source is already included in bindings.cpp
|
|
1080
|
+
if (mod.has_header) {
|
|
1081
|
+
std::string source_path = replace_all(mod.source_path, "\\", "/");
|
|
1082
|
+
sources_file << source_path << "\n";
|
|
1083
|
+
} else {
|
|
1084
|
+
std::cout << "NOTE: Skipping '" << mod.source_path
|
|
1085
|
+
<< "' from compilation (already included as header)" << std::endl;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
if (sources_file.fail() || sources_file.bad()) {
|
|
1090
|
+
std::cerr << "ERROR: Failed to write to file: " << sources_path << std::endl;
|
|
1091
|
+
sources_file.close();
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
sources_file.close();
|
|
1095
|
+
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
std::string API::compute_file_hash(const std::string& filepath) {
|
|
1100
|
+
if (!std::filesystem::exists(filepath)) {
|
|
1101
|
+
return "0";
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
std::ifstream file(filepath, std::ios::binary);
|
|
1105
|
+
if (!file.is_open()) {
|
|
1106
|
+
return "0";
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
std::stringstream buffer;
|
|
1110
|
+
buffer << file.rdbuf();
|
|
1111
|
+
std::string content = buffer.str();
|
|
1112
|
+
|
|
1113
|
+
std::hash<std::string> hasher;
|
|
1114
|
+
size_t hash_value = hasher(content);
|
|
1115
|
+
|
|
1116
|
+
std::ostringstream hash_str;
|
|
1117
|
+
hash_str << std::hex << hash_value;
|
|
1118
|
+
return hash_str.str();
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// v2.3.5: Helper function to trim whitespace
|
|
1122
|
+
static std::string trim_whitespace(const std::string& str) {
|
|
1123
|
+
size_t start = str.find_first_not_of(" \t\n\r");
|
|
1124
|
+
if (start == std::string::npos) return "";
|
|
1125
|
+
size_t end = str.find_last_not_of(" \t\n\r");
|
|
1126
|
+
return str.substr(start, end - start + 1);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// v2.3.5: Parse parameter list from C++ function signature
|
|
1130
|
+
std::vector<ParameterInfo> API::parse_parameter_list(const std::string& params_str) {
|
|
1131
|
+
std::vector<ParameterInfo> parameters;
|
|
1132
|
+
|
|
1133
|
+
if (params_str.empty() || params_str == "void") {
|
|
1134
|
+
return parameters;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Split by comma (respecting template brackets)
|
|
1138
|
+
std::vector<std::string> param_tokens;
|
|
1139
|
+
int bracket_depth = 0;
|
|
1140
|
+
int paren_depth = 0;
|
|
1141
|
+
std::string current_param;
|
|
1142
|
+
|
|
1143
|
+
for (char c : params_str) {
|
|
1144
|
+
if (c == '<') bracket_depth++;
|
|
1145
|
+
else if (c == '>') bracket_depth--;
|
|
1146
|
+
else if (c == '(') paren_depth++;
|
|
1147
|
+
else if (c == ')') paren_depth--;
|
|
1148
|
+
else if (c == ',' && bracket_depth == 0 && paren_depth == 0) {
|
|
1149
|
+
param_tokens.push_back(trim_whitespace(current_param));
|
|
1150
|
+
current_param.clear();
|
|
1151
|
+
continue;
|
|
1152
|
+
}
|
|
1153
|
+
current_param += c;
|
|
1154
|
+
}
|
|
1155
|
+
if (!current_param.empty()) {
|
|
1156
|
+
param_tokens.push_back(trim_whitespace(current_param));
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Parse each parameter
|
|
1160
|
+
for (const auto& param_str : param_tokens) {
|
|
1161
|
+
ParameterInfo param;
|
|
1162
|
+
std::string clean_param = trim_whitespace(param_str);
|
|
1163
|
+
|
|
1164
|
+
// Check for default value
|
|
1165
|
+
size_t eq_pos = clean_param.find('=');
|
|
1166
|
+
if (eq_pos != std::string::npos) {
|
|
1167
|
+
param.default_value = trim_whitespace(clean_param.substr(eq_pos + 1));
|
|
1168
|
+
clean_param = trim_whitespace(clean_param.substr(0, eq_pos));
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Parse type and name using regex
|
|
1172
|
+
// Pattern: [const] type [&|*] name
|
|
1173
|
+
std::regex param_regex(R"(^(const\s+)?(.+?)\s*([&*])?\s*([a-zA-Z_]\w*)$)");
|
|
1174
|
+
std::smatch param_match;
|
|
1175
|
+
|
|
1176
|
+
if (std::regex_match(clean_param, param_match, param_regex)) {
|
|
1177
|
+
if (param_match[1].matched) {
|
|
1178
|
+
param.is_const = true;
|
|
1179
|
+
}
|
|
1180
|
+
param.type = trim_whitespace(param_match[2].str());
|
|
1181
|
+
if (param_match[3].matched) {
|
|
1182
|
+
std::string ref_or_ptr = param_match[3].str();
|
|
1183
|
+
if (ref_or_ptr == "&") param.is_reference = true;
|
|
1184
|
+
if (ref_or_ptr == "*") param.is_pointer = true;
|
|
1185
|
+
}
|
|
1186
|
+
param.name = param_match[4].str();
|
|
1187
|
+
} else {
|
|
1188
|
+
// Fallback: assume entire thing is type, no name
|
|
1189
|
+
param.type = clean_param;
|
|
1190
|
+
param.name = "";
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
parameters.push_back(param);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
return parameters;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
// v2.3.5: Parse C++ function signatures from source file
|
|
1200
|
+
std::vector<FunctionBinding> API::parse_cpp_function_signatures(const std::string& cpp_file_path) {
|
|
1201
|
+
std::vector<FunctionBinding> signatures;
|
|
1202
|
+
|
|
1203
|
+
std::ifstream file(cpp_file_path);
|
|
1204
|
+
if (!file.is_open()) {
|
|
1205
|
+
return signatures;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
std::string content((std::istreambuf_iterator<char>(file)),
|
|
1209
|
+
std::istreambuf_iterator<char>());
|
|
1210
|
+
file.close();
|
|
1211
|
+
|
|
1212
|
+
// Regex pattern for function declarations/definitions
|
|
1213
|
+
// Matches: [qualifiers] return_type function_name(params) [const]
|
|
1214
|
+
std::regex func_regex(
|
|
1215
|
+
R"((?:^|\n)\s*(?:(static|inline|virtual|constexpr)\s+)?)"
|
|
1216
|
+
R"(([a-zA-Z_][\w:<>]*(?:\s*\*|\s*&)?)\s+)"
|
|
1217
|
+
R"(([a-zA-Z_]\w*)\s*\()"
|
|
1218
|
+
R"(([^)]*)\))"
|
|
1219
|
+
R"(\s*(const)?\s*(?:[{;]|\n))"
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
auto search_begin = std::sregex_iterator(content.begin(), content.end(), func_regex);
|
|
1223
|
+
auto search_end = std::sregex_iterator();
|
|
1224
|
+
|
|
1225
|
+
for (std::sregex_iterator i = search_begin; i != search_end; ++i) {
|
|
1226
|
+
std::smatch match = *i;
|
|
1227
|
+
|
|
1228
|
+
FunctionBinding sig;
|
|
1229
|
+
|
|
1230
|
+
// Extract leading qualifier
|
|
1231
|
+
if (match[1].matched) {
|
|
1232
|
+
std::string qualifier = match[1].str();
|
|
1233
|
+
if (qualifier == "static") sig.is_static = true;
|
|
1234
|
+
if (qualifier == "inline") sig.is_inline = true;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// Extract return type
|
|
1238
|
+
sig.return_type = trim_whitespace(match[2].str());
|
|
1239
|
+
|
|
1240
|
+
// Extract function name
|
|
1241
|
+
sig.function_name = trim_whitespace(match[3].str());
|
|
1242
|
+
|
|
1243
|
+
// Extract parameters
|
|
1244
|
+
std::string params_str = trim_whitespace(match[4].str());
|
|
1245
|
+
if (!params_str.empty() && params_str != "void") {
|
|
1246
|
+
sig.parameters = parse_parameter_list(params_str);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// Extract trailing const
|
|
1250
|
+
if (match[5].matched) {
|
|
1251
|
+
sig.is_const = true;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// Build full signature string
|
|
1255
|
+
sig.full_signature = match[0].str();
|
|
1256
|
+
|
|
1257
|
+
signatures.push_back(sig);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
return signatures;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
std::string API::generate_registry_json(const std::vector<ModuleDescriptor>& modules, const std::string& plugins_dir) {
|
|
1264
|
+
std::ostringstream json;
|
|
1265
|
+
json << "{\n";
|
|
1266
|
+
json << " \"schema_version\": \"2.0\",\n";
|
|
1267
|
+
json << " \"modules\": {\n";
|
|
1268
|
+
|
|
1269
|
+
for (size_t i = 0; i < modules.size(); ++i) {
|
|
1270
|
+
const auto& mod = modules[i];
|
|
1271
|
+
|
|
1272
|
+
json << " \"" << mod.module_name << "\": {\n";
|
|
1273
|
+
json << " \"sources\": [";
|
|
1274
|
+
|
|
1275
|
+
std::string source_path = replace_all(mod.source_path, "\\", "/");
|
|
1276
|
+
json << "\"" << source_path << "\"";
|
|
1277
|
+
|
|
1278
|
+
// v2.0: Add additional sources
|
|
1279
|
+
for (const auto& add_src : mod.additional_sources) {
|
|
1280
|
+
std::string add_src_path = replace_all(add_src, "\\", "/");
|
|
1281
|
+
json << ", \"" << add_src_path << "\"";
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
json << "],\n";
|
|
1285
|
+
|
|
1286
|
+
if (mod.has_header) {
|
|
1287
|
+
std::string header_path = replace_all(mod.header_path, "\\", "/");
|
|
1288
|
+
json << " \"header\": \"" << header_path << "\",\n";
|
|
1289
|
+
} else {
|
|
1290
|
+
json << " \"header\": null,\n";
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
std::filesystem::path cp_path = std::filesystem::path(plugins_dir) / (mod.module_name + ".cp");
|
|
1294
|
+
std::string cp_file = cp_path.string();
|
|
1295
|
+
cp_file = replace_all(cp_file, "\\", "/");
|
|
1296
|
+
json << " \"cp_file\": \"" << cp_file << "\",\n";
|
|
1297
|
+
|
|
1298
|
+
// v2.0: Add dependencies
|
|
1299
|
+
json << " \"dependencies\": [\n";
|
|
1300
|
+
for (size_t j = 0; j < mod.dependencies.size(); ++j) {
|
|
1301
|
+
const auto& dep = mod.dependencies[j];
|
|
1302
|
+
json << " {\n";
|
|
1303
|
+
json << " \"target\": \"" << dep.target_module << "\"";
|
|
1304
|
+
if (!dep.required_types.empty()) {
|
|
1305
|
+
json << ",\n \"types\": [";
|
|
1306
|
+
for (size_t k = 0; k < dep.required_types.size(); ++k) {
|
|
1307
|
+
json << "\"" << dep.required_types[k] << "\"";
|
|
1308
|
+
if (k < dep.required_types.size() - 1) json << ", ";
|
|
1309
|
+
}
|
|
1310
|
+
json << "]";
|
|
1311
|
+
}
|
|
1312
|
+
if (dep.is_optional) {
|
|
1313
|
+
json << ",\n \"optional\": true";
|
|
1314
|
+
} else {
|
|
1315
|
+
json << ",\n \"optional\": false";
|
|
1316
|
+
}
|
|
1317
|
+
json << "\n }";
|
|
1318
|
+
if (j < mod.dependencies.size() - 1) json << ",";
|
|
1319
|
+
json << "\n";
|
|
1320
|
+
}
|
|
1321
|
+
json << " ],\n";
|
|
1322
|
+
|
|
1323
|
+
// v2.3.5: Use source_hashes with full path keys for compatibility
|
|
1324
|
+
json << " \"source_hashes\": {\n";
|
|
1325
|
+
|
|
1326
|
+
// Hash main source with full relative path as key
|
|
1327
|
+
json << " \"" << source_path << "\": \"" << compute_file_hash(mod.source_path) << "\"";
|
|
1328
|
+
|
|
1329
|
+
// Hash additional sources
|
|
1330
|
+
for (const auto& add_src : mod.additional_sources) {
|
|
1331
|
+
std::string add_src_path = replace_all(add_src, "\\", "/");
|
|
1332
|
+
json << ",\n \"" << add_src_path << "\": \"" << compute_file_hash(add_src) << "\"";
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// Hash header if exists
|
|
1336
|
+
if (mod.has_header) {
|
|
1337
|
+
std::string header_path = replace_all(mod.header_path, "\\", "/");
|
|
1338
|
+
json << ",\n \"" << header_path << "\": \"" << compute_file_hash(mod.header_path) << "\"";
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// Hash .cp file with special key format (for compatibility with Python code)
|
|
1342
|
+
json << ",\n \"" << mod.module_name << ".cp\": \"" << compute_file_hash(cp_file) << "\"";
|
|
1343
|
+
json << "\n },\n";
|
|
1344
|
+
|
|
1345
|
+
// v2.0: Add structs with fields
|
|
1346
|
+
json << " \"structs\": [\n";
|
|
1347
|
+
for (size_t j = 0; j < mod.structs.size(); ++j) {
|
|
1348
|
+
const auto& st = mod.structs[j];
|
|
1349
|
+
json << " {\n";
|
|
1350
|
+
json << " \"name\": \"" << st.struct_name << "\"";
|
|
1351
|
+
if (!st.documentation.empty()) {
|
|
1352
|
+
json << ",\n \"doc\": \"" << replace_all(st.documentation, "\"", "\\\"") << "\"";
|
|
1353
|
+
}
|
|
1354
|
+
if (st.is_template) {
|
|
1355
|
+
json << ",\n \"is_template\": true";
|
|
1356
|
+
json << ",\n \"template_types\": [";
|
|
1357
|
+
for (size_t k = 0; k < st.template_types.size(); ++k) {
|
|
1358
|
+
json << "\"" << st.template_types[k] << "\"";
|
|
1359
|
+
if (k < st.template_types.size() - 1) json << ", ";
|
|
1360
|
+
}
|
|
1361
|
+
json << "]";
|
|
1362
|
+
} else {
|
|
1363
|
+
json << ",\n \"is_template\": false";
|
|
1364
|
+
}
|
|
1365
|
+
json << ",\n \"fields\": [\n";
|
|
1366
|
+
for (size_t k = 0; k < st.fields.size(); ++k) {
|
|
1367
|
+
const auto& [type, name] = st.fields[k];
|
|
1368
|
+
json << " {\"type\": \"" << type << "\", \"name\": \"" << name << "\"}";
|
|
1369
|
+
if (k < st.fields.size() - 1) json << ",";
|
|
1370
|
+
json << "\n";
|
|
1371
|
+
}
|
|
1372
|
+
json << " ]\n";
|
|
1373
|
+
json << " }";
|
|
1374
|
+
if (j < mod.structs.size() - 1) json << ",";
|
|
1375
|
+
json << "\n";
|
|
1376
|
+
}
|
|
1377
|
+
json << " ],\n";
|
|
1378
|
+
|
|
1379
|
+
// Add functions with enhanced signature metadata (v2.3.5)
|
|
1380
|
+
json << " \"functions\": [\n";
|
|
1381
|
+
for (size_t j = 0; j < mod.functions.size(); ++j) {
|
|
1382
|
+
const auto& func = mod.functions[j];
|
|
1383
|
+
json << " {\n";
|
|
1384
|
+
json << " \"name\": \"" << func.function_name << "\"";
|
|
1385
|
+
|
|
1386
|
+
if (!func.documentation.empty()) {
|
|
1387
|
+
json << ",\n \"doc\": \"" << replace_all(func.documentation, "\"", "\\\"") << "\"";
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// v2.3.5: Add return type
|
|
1391
|
+
json << ",\n \"return_type\": \"" << func.return_type << "\"";
|
|
1392
|
+
|
|
1393
|
+
// v2.3.5: Add parameters with types
|
|
1394
|
+
json << ",\n \"parameters\": [\n";
|
|
1395
|
+
for (size_t k = 0; k < func.parameters.size(); ++k) {
|
|
1396
|
+
const auto& param = func.parameters[k];
|
|
1397
|
+
json << " {\n";
|
|
1398
|
+
json << " \"name\": \"" << param.name << "\",\n";
|
|
1399
|
+
json << " \"type\": \"" << param.type << "\"";
|
|
1400
|
+
if (!param.default_value.empty()) {
|
|
1401
|
+
json << ",\n \"default\": \"" << replace_all(param.default_value, "\"", "\\\"") << "\"";
|
|
1402
|
+
}
|
|
1403
|
+
if (param.is_const) json << ",\n \"const\": true";
|
|
1404
|
+
if (param.is_reference) json << ",\n \"reference\": true";
|
|
1405
|
+
if (param.is_pointer) json << ",\n \"pointer\": true";
|
|
1406
|
+
json << "\n }";
|
|
1407
|
+
if (k < func.parameters.size() - 1) json << ",";
|
|
1408
|
+
json << "\n";
|
|
1409
|
+
}
|
|
1410
|
+
json << " ]";
|
|
1411
|
+
|
|
1412
|
+
// v2.3.5: Add function qualifiers
|
|
1413
|
+
if (func.is_static) json << ",\n \"static\": true";
|
|
1414
|
+
if (func.is_const) json << ",\n \"const\": true";
|
|
1415
|
+
if (func.is_inline) json << ",\n \"inline\": true";
|
|
1416
|
+
|
|
1417
|
+
// v3.1.5: Add template function info
|
|
1418
|
+
if (func.is_template) {
|
|
1419
|
+
json << ",\n \"is_template\": true";
|
|
1420
|
+
json << ",\n \"template_types\": [";
|
|
1421
|
+
for (size_t k = 0; k < func.template_types.size(); ++k) {
|
|
1422
|
+
json << "\"" << func.template_types[k] << "\"";
|
|
1423
|
+
if (k < func.template_types.size() - 1) json << ", ";
|
|
1424
|
+
}
|
|
1425
|
+
json << "]";
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
json << "\n }";
|
|
1429
|
+
if (j < mod.functions.size() - 1) json << ",";
|
|
1430
|
+
json << "\n";
|
|
1431
|
+
}
|
|
1432
|
+
json << " ],\n";
|
|
1433
|
+
|
|
1434
|
+
// Add classes with methods, constructors, and documentation
|
|
1435
|
+
json << " \"classes\": [\n";
|
|
1436
|
+
for (size_t j = 0; j < mod.classes.size(); ++j) {
|
|
1437
|
+
const auto& cls = mod.classes[j];
|
|
1438
|
+
json << " {\n";
|
|
1439
|
+
json << " \"name\": \"" << cls.class_name << "\"";
|
|
1440
|
+
if (!cls.documentation.empty()) {
|
|
1441
|
+
json << ",\n \"doc\": \"" << replace_all(cls.documentation, "\"", "\\\"") << "\"";
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// v2.4.3: Add constructor signatures
|
|
1445
|
+
json << ",\n \"constructors\": [\n";
|
|
1446
|
+
if (cls.constructors.empty()) {
|
|
1447
|
+
// Default constructor
|
|
1448
|
+
json << " {\"params\": []}\n";
|
|
1449
|
+
} else {
|
|
1450
|
+
for (size_t k = 0; k < cls.constructors.size(); ++k) {
|
|
1451
|
+
const auto& ctor = cls.constructors[k];
|
|
1452
|
+
json << " {\"params\": [";
|
|
1453
|
+
for (size_t p = 0; p < ctor.param_types.size(); ++p) {
|
|
1454
|
+
json << "\"" << ctor.param_types[p] << "\"";
|
|
1455
|
+
if (p < ctor.param_types.size() - 1) json << ", ";
|
|
1456
|
+
}
|
|
1457
|
+
json << "]}";
|
|
1458
|
+
if (k < cls.constructors.size() - 1) json << ",";
|
|
1459
|
+
json << "\n";
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
json << " ]";
|
|
1463
|
+
|
|
1464
|
+
json << ",\n \"methods\": [\n";
|
|
1465
|
+
if (!cls.method_signatures.empty()) {
|
|
1466
|
+
for (size_t k = 0; k < cls.method_signatures.size(); ++k) {
|
|
1467
|
+
const auto& sig = cls.method_signatures[k];
|
|
1468
|
+
json << " {\n";
|
|
1469
|
+
json << " \"name\": \"" << sig.name << "\"";
|
|
1470
|
+
if (!sig.documentation.empty()) {
|
|
1471
|
+
json << ",\n \"doc\": \"" << replace_all(sig.documentation, "\"", "\\\"") << "\"";
|
|
1472
|
+
}
|
|
1473
|
+
json << ",\n \"return_type\": \"" << sig.return_type << "\"";
|
|
1474
|
+
json << ",\n \"parameters\": [\n";
|
|
1475
|
+
for (size_t p = 0; p < sig.parameters.size(); ++p) {
|
|
1476
|
+
const auto& param = sig.parameters[p];
|
|
1477
|
+
json << " {\n";
|
|
1478
|
+
json << " \"name\": \"" << param.name << "\",\n";
|
|
1479
|
+
json << " \"type\": \"" << param.type << "\"";
|
|
1480
|
+
if (!param.default_value.empty()) {
|
|
1481
|
+
json << ",\n \"default\": \"" << replace_all(param.default_value, "\"", "\\\"") << "\"";
|
|
1482
|
+
}
|
|
1483
|
+
if (param.is_const) json << ",\n \"const\": true";
|
|
1484
|
+
if (param.is_reference) json << ",\n \"reference\": true";
|
|
1485
|
+
if (param.is_pointer) json << ",\n \"pointer\": true";
|
|
1486
|
+
json << "\n }";
|
|
1487
|
+
if (p < sig.parameters.size() - 1) json << ",";
|
|
1488
|
+
json << "\n";
|
|
1489
|
+
}
|
|
1490
|
+
json << " ]";
|
|
1491
|
+
if (sig.is_const) json << ",\n \"const\": true";
|
|
1492
|
+
if (sig.is_static) json << ",\n \"static\": true";
|
|
1493
|
+
json << "\n }";
|
|
1494
|
+
if (k < cls.method_signatures.size() - 1) json << ",";
|
|
1495
|
+
json << "\n";
|
|
1496
|
+
}
|
|
1497
|
+
} else {
|
|
1498
|
+
for (size_t k = 0; k < cls.methods.size(); ++k) {
|
|
1499
|
+
const auto& method = cls.methods[k];
|
|
1500
|
+
json << " {\n";
|
|
1501
|
+
json << " \"name\": \"" << method << "\"";
|
|
1502
|
+
if (cls.method_docs.count(method)) {
|
|
1503
|
+
json << ",\n \"doc\": \"" << replace_all(cls.method_docs.at(method), "\"", "\\\"") << "\"";
|
|
1504
|
+
}
|
|
1505
|
+
json << "\n }";
|
|
1506
|
+
if (k < cls.methods.size() - 1) json << ",";
|
|
1507
|
+
json << "\n";
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
json << " ]\n";
|
|
1511
|
+
json << " }";
|
|
1512
|
+
if (j < mod.classes.size() - 1) json << ",";
|
|
1513
|
+
json << "\n";
|
|
1514
|
+
}
|
|
1515
|
+
json << " ],\n";
|
|
1516
|
+
|
|
1517
|
+
auto t = std::time(nullptr);
|
|
1518
|
+
std::tm tm_buffer;
|
|
1519
|
+
#ifdef _WIN32
|
|
1520
|
+
localtime_s(&tm_buffer, &t);
|
|
1521
|
+
#else
|
|
1522
|
+
localtime_r(&t, &tm_buffer);
|
|
1523
|
+
#endif
|
|
1524
|
+
std::ostringstream timestamp;
|
|
1525
|
+
timestamp << std::put_time(&tm_buffer, "%Y-%m-%dT%H:%M:%S");
|
|
1526
|
+
json << " \"last_built\": \"" << timestamp.str() << "\"\n";
|
|
1527
|
+
|
|
1528
|
+
json << " }";
|
|
1529
|
+
if (i < modules.size() - 1) {
|
|
1530
|
+
json << ",";
|
|
1531
|
+
}
|
|
1532
|
+
json << "\n";
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
json << " }\n";
|
|
1536
|
+
json << "}\n";
|
|
1537
|
+
return json.str();
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
std::string API::trim(const std::string& str) {
|
|
1541
|
+
size_t start = 0;
|
|
1542
|
+
while (start < str.length() && std::isspace(static_cast<unsigned char>(str[start]))) start++;
|
|
1543
|
+
|
|
1544
|
+
size_t end = str.length();
|
|
1545
|
+
while (end > start && std::isspace(static_cast<unsigned char>(str[end - 1]))) end--;
|
|
1546
|
+
|
|
1547
|
+
return str.substr(start, end - start);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
std::vector<std::string> API::split(const std::string& str, char delimiter) {
|
|
1551
|
+
std::vector<std::string> tokens;
|
|
1552
|
+
std::stringstream ss(str);
|
|
1553
|
+
std::string token;
|
|
1554
|
+
|
|
1555
|
+
while (std::getline(ss, token, delimiter)) {
|
|
1556
|
+
tokens.push_back(token);
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
return tokens;
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
std::string API::extract_between(const std::string& str, char open, char close) {
|
|
1563
|
+
size_t start = str.find(open);
|
|
1564
|
+
size_t end = str.find(close, start);
|
|
1565
|
+
|
|
1566
|
+
if (start == std::string::npos || end == std::string::npos) {
|
|
1567
|
+
return "";
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
return str.substr(start + 1, end - start - 1);
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
std::string API::safe_extract_between(const std::string& str,
|
|
1574
|
+
size_t start_pos,
|
|
1575
|
+
size_t end_pos,
|
|
1576
|
+
const std::string& context) {
|
|
1577
|
+
if (start_pos == std::string::npos || end_pos == std::string::npos) {
|
|
1578
|
+
throw std::runtime_error("Parse error: missing parenthesis in " + context);
|
|
1579
|
+
}
|
|
1580
|
+
if (start_pos >= end_pos) {
|
|
1581
|
+
throw std::runtime_error("Parse error: invalid syntax in " + context +
|
|
1582
|
+
" (closing parenthesis before opening)");
|
|
1583
|
+
}
|
|
1584
|
+
if (end_pos > str.length()) {
|
|
1585
|
+
throw std::runtime_error("Parse error: position out of bounds in " + context);
|
|
1586
|
+
}
|
|
1587
|
+
return trim(str.substr(start_pos + 1, end_pos - start_pos - 1));
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
bool API::starts_with(const std::string& str, const std::string& prefix) {
|
|
1591
|
+
return str.size() >= prefix.size() &&
|
|
1592
|
+
str.compare(0, prefix.size(), prefix) == 0;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
std::string API::normalize_path(const std::string& path) {
|
|
1596
|
+
std::string p = trim(path);
|
|
1597
|
+
std::replace(p.begin(), p.end(), '/', '\\');
|
|
1598
|
+
return p;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
std::string API::replace_all(std::string str, const std::string& from, const std::string& to) {
|
|
1602
|
+
size_t start_pos = 0;
|
|
1603
|
+
while((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
|
1604
|
+
str.replace(start_pos, from.length(), to);
|
|
1605
|
+
start_pos += to.length();
|
|
1606
|
+
}
|
|
1607
|
+
return str;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
std::string API::extract_doc_string(const std::string& str) {
|
|
1611
|
+
// Extract string from DOC(..., "...") - get the part after comma
|
|
1612
|
+
size_t comma = str.find(',');
|
|
1613
|
+
if (comma == std::string::npos) return "";
|
|
1614
|
+
|
|
1615
|
+
std::string after_comma = str.substr(comma + 1);
|
|
1616
|
+
after_comma = trim(after_comma);
|
|
1617
|
+
|
|
1618
|
+
// Find quoted string
|
|
1619
|
+
size_t quote1 = after_comma.find('"');
|
|
1620
|
+
if (quote1 == std::string::npos) return "";
|
|
1621
|
+
|
|
1622
|
+
size_t quote2 = after_comma.find('"', quote1 + 1);
|
|
1623
|
+
if (quote2 == std::string::npos) return "";
|
|
1624
|
+
|
|
1625
|
+
return after_comma.substr(quote1 + 1, quote2 - quote1 - 1);
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
std::map<std::string, std::string> API::parse_doc_statements(const std::string& content) {
|
|
1629
|
+
std::map<std::string, std::string> docs;
|
|
1630
|
+
|
|
1631
|
+
// Find all DOC(...) statements
|
|
1632
|
+
size_t pos = 0;
|
|
1633
|
+
while ((pos = content.find("DOC(", pos)) != std::string::npos) {
|
|
1634
|
+
// Find matching closing parenthesis
|
|
1635
|
+
int paren_count = 1;
|
|
1636
|
+
size_t i = pos + 4; // Start after "DOC("
|
|
1637
|
+
size_t doc_start = i;
|
|
1638
|
+
|
|
1639
|
+
while (i < content.length() && paren_count > 0) {
|
|
1640
|
+
if (content[i] == '(') paren_count++;
|
|
1641
|
+
else if (content[i] == ')') paren_count--;
|
|
1642
|
+
i++;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
if (paren_count == 0) {
|
|
1646
|
+
// Extract full DOC(...) content
|
|
1647
|
+
std::string doc_content = content.substr(doc_start, i - doc_start - 1);
|
|
1648
|
+
|
|
1649
|
+
// Find first closing paren to get the key (FUNC(name), CLASS(name), METHOD(name))
|
|
1650
|
+
size_t first_close = doc_content.find(')');
|
|
1651
|
+
if (first_close != std::string::npos) {
|
|
1652
|
+
std::string key = doc_content.substr(0, first_close + 1);
|
|
1653
|
+
key = trim(key);
|
|
1654
|
+
|
|
1655
|
+
// Extract doc string
|
|
1656
|
+
std::string doc_str = extract_doc_string(doc_content);
|
|
1657
|
+
|
|
1658
|
+
if (!key.empty() && !doc_str.empty()) {
|
|
1659
|
+
docs[key] = doc_str;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
pos = i;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
return docs;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// v2.0: TypeRegistry method implementations
|
|
1671
|
+
void TypeRegistry::register_struct(const std::string& module, const StructBinding& s) {
|
|
1672
|
+
structs_[s.struct_name] = s;
|
|
1673
|
+
type_to_module_[s.struct_name] = module;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
void TypeRegistry::register_class(const std::string& module, const ClassBinding& c) {
|
|
1677
|
+
classes_[c.class_name] = c;
|
|
1678
|
+
type_to_module_[c.class_name] = module;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
void TypeRegistry::register_dependency(const std::string& from_module, const ModuleDependency& dep) {
|
|
1682
|
+
dependencies_[from_module].push_back(dep);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
TypeMetadata TypeRegistry::resolve_type(const std::string& type_string) const {
|
|
1686
|
+
TypeMetadata meta;
|
|
1687
|
+
// Basic implementation - can be enhanced with TypeResolver
|
|
1688
|
+
meta.full_signature = type_string;
|
|
1689
|
+
meta.base_type = type_string;
|
|
1690
|
+
|
|
1691
|
+
if (type_string.find("vector") != std::string::npos || type_string.find("std::vector") != std::string::npos) {
|
|
1692
|
+
meta.category = TypeMetadata::VECTOR_TYPE;
|
|
1693
|
+
} else if (type_string.find("map") != std::string::npos || type_string.find("std::map") != std::string::npos) {
|
|
1694
|
+
meta.category = TypeMetadata::MAP_TYPE;
|
|
1695
|
+
} else if (is_struct(type_string)) {
|
|
1696
|
+
meta.category = TypeMetadata::STRUCT_TYPE;
|
|
1697
|
+
} else if (is_class(type_string)) {
|
|
1698
|
+
meta.category = TypeMetadata::CLASS_TYPE;
|
|
1699
|
+
} else {
|
|
1700
|
+
meta.category = TypeMetadata::PRIMITIVE;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
return meta;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
bool TypeRegistry::is_struct(const std::string& type_name) const {
|
|
1707
|
+
return structs_.count(type_name) > 0;
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
bool TypeRegistry::is_class(const std::string& type_name) const {
|
|
1711
|
+
return classes_.count(type_name) > 0;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
bool TypeRegistry::type_exists(const std::string& type_name) const {
|
|
1715
|
+
return type_to_module_.count(type_name) > 0;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
std::vector<std::string> TypeRegistry::get_dependencies(const std::string& module) const {
|
|
1719
|
+
std::vector<std::string> result;
|
|
1720
|
+
if (dependencies_.count(module)) {
|
|
1721
|
+
for (const auto& dep : dependencies_.at(module)) {
|
|
1722
|
+
result.push_back(dep.target_module);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
return result;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
std::vector<std::string> TypeRegistry::get_dependency_order() const {
|
|
1729
|
+
return topological_sort();
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
bool TypeRegistry::has_circular_dependency() const {
|
|
1733
|
+
std::set<std::string> visited;
|
|
1734
|
+
std::set<std::string> rec_stack;
|
|
1735
|
+
std::vector<std::string> path;
|
|
1736
|
+
|
|
1737
|
+
for (const auto& [module, _] : dependencies_) {
|
|
1738
|
+
if (has_cycle_dfs(module, visited, rec_stack, path)) {
|
|
1739
|
+
return true;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
std::vector<std::string> TypeRegistry::get_circular_path() const {
|
|
1747
|
+
std::set<std::string> visited;
|
|
1748
|
+
std::set<std::string> rec_stack;
|
|
1749
|
+
std::vector<std::string> path;
|
|
1750
|
+
|
|
1751
|
+
for (const auto& [module, _] : dependencies_) {
|
|
1752
|
+
if (has_cycle_dfs(module, visited, rec_stack, path)) {
|
|
1753
|
+
return path;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
return {};
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
std::string TypeRegistry::get_module_for_type(const std::string& type_name) const {
|
|
1761
|
+
if (type_to_module_.count(type_name)) {
|
|
1762
|
+
return type_to_module_.at(type_name);
|
|
1763
|
+
}
|
|
1764
|
+
return "";
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
std::vector<std::string> TypeRegistry::get_all_modules() const {
|
|
1768
|
+
std::set<std::string> modules;
|
|
1769
|
+
for (const auto& [type, module] : type_to_module_) {
|
|
1770
|
+
modules.insert(module);
|
|
1771
|
+
}
|
|
1772
|
+
return std::vector<std::string>(modules.begin(), modules.end());
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
std::vector<std::string> TypeRegistry::topological_sort() const {
|
|
1776
|
+
std::map<std::string, int> in_degree;
|
|
1777
|
+
std::set<std::string> all_modules;
|
|
1778
|
+
|
|
1779
|
+
// Get all modules
|
|
1780
|
+
for (const auto& [module, _] : dependencies_) {
|
|
1781
|
+
all_modules.insert(module);
|
|
1782
|
+
in_degree[module] = 0;
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
// Calculate in-degrees
|
|
1786
|
+
for (const auto& [module, deps] : dependencies_) {
|
|
1787
|
+
for (const auto& dep : deps) {
|
|
1788
|
+
all_modules.insert(dep.target_module);
|
|
1789
|
+
in_degree[dep.target_module]++;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
// Kahn's algorithm
|
|
1794
|
+
std::vector<std::string> result;
|
|
1795
|
+
std::vector<std::string> queue;
|
|
1796
|
+
|
|
1797
|
+
// Find all nodes with in-degree 0
|
|
1798
|
+
for (const auto& module : all_modules) {
|
|
1799
|
+
if (in_degree[module] == 0) {
|
|
1800
|
+
queue.push_back(module);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
while (!queue.empty()) {
|
|
1805
|
+
std::string current = queue.back();
|
|
1806
|
+
queue.pop_back();
|
|
1807
|
+
result.push_back(current);
|
|
1808
|
+
|
|
1809
|
+
// Reduce in-degree for neighbors
|
|
1810
|
+
if (dependencies_.count(current)) {
|
|
1811
|
+
for (const auto& dep : dependencies_.at(current)) {
|
|
1812
|
+
in_degree[dep.target_module]--;
|
|
1813
|
+
if (in_degree[dep.target_module] == 0) {
|
|
1814
|
+
queue.push_back(dep.target_module);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
return result;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1823
|
+
bool TypeRegistry::has_cycle_dfs(const std::string& module,
|
|
1824
|
+
std::set<std::string>& visited,
|
|
1825
|
+
std::set<std::string>& rec_stack,
|
|
1826
|
+
std::vector<std::string>& path) const {
|
|
1827
|
+
visited.insert(module);
|
|
1828
|
+
rec_stack.insert(module);
|
|
1829
|
+
path.push_back(module);
|
|
1830
|
+
|
|
1831
|
+
if (dependencies_.count(module)) {
|
|
1832
|
+
for (const auto& dep : dependencies_.at(module)) {
|
|
1833
|
+
if (!visited.count(dep.target_module)) {
|
|
1834
|
+
if (has_cycle_dfs(dep.target_module, visited, rec_stack, path)) {
|
|
1835
|
+
return true;
|
|
1836
|
+
}
|
|
1837
|
+
} else if (rec_stack.count(dep.target_module)) {
|
|
1838
|
+
// Found cycle
|
|
1839
|
+
path.push_back(dep.target_module);
|
|
1840
|
+
return true;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
rec_stack.erase(module);
|
|
1846
|
+
path.pop_back();
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// v2.0: TypeMetadata conversion methods implementation
|
|
1851
|
+
std::string TypeMetadata::to_python_type_hint() const {
|
|
1852
|
+
static const std::map<std::string, std::string> type_map = {
|
|
1853
|
+
{"int", "int"}, {"long", "int"}, {"short", "int"},
|
|
1854
|
+
{"float", "float"}, {"double", "float"},
|
|
1855
|
+
{"bool", "bool"},
|
|
1856
|
+
{"string", "str"}, {"std::string", "str"},
|
|
1857
|
+
{"void", "None"}
|
|
1858
|
+
};
|
|
1859
|
+
|
|
1860
|
+
if (type_map.count(base_type)) {
|
|
1861
|
+
return type_map.at(base_type);
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
if (category == VECTOR_TYPE && !template_args.empty()) {
|
|
1865
|
+
return "List[" + template_args[0].to_python_type_hint() + "]";
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
if (category == MAP_TYPE && template_args.size() >= 2) {
|
|
1869
|
+
return "Dict[" + template_args[0].to_python_type_hint() + ", " +
|
|
1870
|
+
template_args[1].to_python_type_hint() + "]";
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// For custom types (struct/class)
|
|
1874
|
+
return base_type;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
std::string TypeMetadata::to_pybind11_type() const {
|
|
1878
|
+
std::ostringstream oss;
|
|
1879
|
+
if (is_const) oss << "const ";
|
|
1880
|
+
oss << base_type;
|
|
1881
|
+
|
|
1882
|
+
if (!template_args.empty()) {
|
|
1883
|
+
oss << "<";
|
|
1884
|
+
for (size_t i = 0; i < template_args.size(); ++i) {
|
|
1885
|
+
if (i > 0) oss << ", ";
|
|
1886
|
+
oss << template_args[i].to_pybind11_type();
|
|
1887
|
+
}
|
|
1888
|
+
oss << ">";
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
if (is_pointer) oss << "*";
|
|
1892
|
+
if (is_reference) oss << "&";
|
|
1893
|
+
|
|
1894
|
+
return oss.str();
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
std::string TypeMetadata::to_cpp_type() const {
|
|
1898
|
+
return to_pybind11_type(); // Same as pybind11 type
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
int main(int argc, char* argv[]) {
|
|
1902
|
+
return API::main(argc, argv);
|
|
1903
|
+
}
|